@saltcorn/markup 0.6.2-beta.2 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/builder.d.ts +18 -0
  2. package/dist/builder.d.ts.map +1 -0
  3. package/dist/builder.js +57 -0
  4. package/dist/builder.js.map +1 -0
  5. package/dist/emergency_layout.d.ts +6 -0
  6. package/dist/emergency_layout.d.ts.map +1 -0
  7. package/dist/emergency_layout.js +38 -0
  8. package/dist/emergency_layout.js.map +1 -0
  9. package/dist/form.d.ts +12 -0
  10. package/dist/form.d.ts.map +1 -0
  11. package/dist/form.js +372 -0
  12. package/dist/form.js.map +1 -0
  13. package/dist/helpers.d.ts +58 -0
  14. package/dist/helpers.d.ts.map +1 -0
  15. package/dist/helpers.js +169 -0
  16. package/dist/helpers.js.map +1 -0
  17. package/dist/index.d.ts +21 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +158 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/layout.d.ts +22 -0
  22. package/dist/layout.d.ts.map +1 -0
  23. package/dist/layout.js +338 -0
  24. package/dist/layout.js.map +1 -0
  25. package/dist/layout_utils.d.ts +21 -0
  26. package/dist/layout_utils.d.ts.map +1 -0
  27. package/dist/layout_utils.js +272 -0
  28. package/dist/layout_utils.js.map +1 -0
  29. package/dist/mktag.d.ts +12 -0
  30. package/dist/mktag.d.ts.map +1 -0
  31. package/dist/mktag.js +100 -0
  32. package/dist/mktag.js.map +1 -0
  33. package/dist/table.d.ts +22 -0
  34. package/dist/table.d.ts.map +1 -0
  35. package/dist/table.js +51 -0
  36. package/dist/table.js.map +1 -0
  37. package/dist/tabs.d.ts +7 -0
  38. package/dist/tabs.d.ts.map +1 -0
  39. package/dist/tabs.js +34 -0
  40. package/dist/tabs.js.map +1 -0
  41. package/dist/tags.d.ts +17 -0
  42. package/dist/tags.d.ts.map +1 -0
  43. package/dist/tags.js +71 -0
  44. package/dist/tags.js.map +1 -0
  45. package/dist/tsconfig.ref.tsbuildinfo +1 -0
  46. package/package.json +25 -6
  47. package/builder.js +0 -101
  48. package/emergency_layout.js +0 -54
  49. package/form.js +0 -603
  50. package/form.test.js +0 -98
  51. package/helpers.js +0 -268
  52. package/index.js +0 -226
  53. package/layout.js +0 -590
  54. package/layout.test.js +0 -39
  55. package/layout_utils.js +0 -394
  56. package/markup.test.js +0 -104
  57. package/mktag.js +0 -105
  58. package/table.js +0 -115
  59. package/tabs.js +0 -54
  60. package/tags.js +0 -56
@@ -1,54 +0,0 @@
1
- /**
2
- * @category saltcorn-markup
3
- * @module emergency_layout
4
- */
5
-
6
- const {
7
- ul,
8
- li,
9
- a,
10
- span,
11
- hr,
12
- div,
13
- text,
14
- i,
15
- h6,
16
- h1,
17
- p,
18
- header,
19
- img,
20
- footer,
21
- } = require("./tags");
22
- const renderLayout = require("./layout");
23
- const { renderForm, link } = require(".");
24
- const { navbar, alert } = require("./layout_utils");
25
-
26
- /**
27
- * @param {string} title
28
- * @param {string|object} body
29
- * @param {object[]} alerts
30
- * @returns {string}
31
- */
32
- const renderBody = (title, body, alerts) =>
33
- renderLayout({
34
- blockDispatch: {},
35
- layout:
36
- typeof body === "string" ? { type: "card", title, contents: body } : body,
37
- alerts,
38
- });
39
-
40
- /**
41
- * @param {object} opts
42
- * @param {string} opts.title
43
- * @param {object} opts.menu
44
- * @param {object} opts.brand
45
- * @param {object[]} opts.alerts
46
- * @param {string} opts.currentUrl
47
- * @param {string|object} opts.body
48
- * @param {object[]} opts.headers
49
- * @returns {string}
50
- */
51
- const wrap = ({ title, menu, brand, alerts, currentUrl, body, headers }) =>
52
- navbar(brand, menu, currentUrl) + renderBody(title, body, alerts);
53
-
54
- module.exports = wrap;
package/form.js DELETED
@@ -1,603 +0,0 @@
1
- /**
2
- * @category saltcorn-markup
3
- * @module form
4
- */
5
-
6
- const {
7
- p,
8
- div,
9
- i,
10
- label,
11
- text,
12
- text_attr,
13
- button,
14
- a,
15
- h5,
16
- span,
17
- } = require("./tags");
18
- const { contract, is } = require("contractis");
19
- const renderLayout = require("./layout");
20
- const { isdef, select_options, search_bar } = require("./helpers");
21
-
22
- /**
23
- * @param {string} s
24
- * @returns {string}
25
- */
26
- const rmInitialDot = (s) => (s && s[0] === "." ? s.replace(".", "") : s);
27
-
28
- /**
29
- * @param {object} sIf
30
- * @returns {string}
31
- */
32
- const mkShowIf = (sIf) =>
33
- Object.entries(sIf)
34
- .map(([target, value]) =>
35
- typeof value === "boolean"
36
- ? `e.closest('.form-namespace').find('[data-fieldname=${rmInitialDot(
37
- target
38
- )}]').prop('checked')===${JSON.stringify(value)}`
39
- : Array.isArray(value)
40
- ? `[${value
41
- .map((v) => `'${v}'`)
42
- .join()}].includes(e.closest('.form-namespace').find('[data-fieldname=${rmInitialDot(
43
- target
44
- )}]').val())`
45
- : `e.closest('.form-namespace').find('[data-fieldname=${rmInitialDot(
46
- target
47
- )}]').val()==='${value}'`
48
- )
49
- .join(" && ");
50
-
51
- /**
52
- * @param {string} formStyle
53
- * @returns {boolean}
54
- */
55
- const isHoriz = (formStyle) => formStyle === "horiz";
56
-
57
- /**
58
- * @param {object} hdr
59
- * @param {object} inner
60
- * @param {string} [error = ""]
61
- * @param {string} fStyle
62
- * @param {string} labelCols
63
- * @returns {div}
64
- */
65
- const formRowWrap = (hdr, inner, error = "", fStyle, labelCols) =>
66
- div(
67
- {
68
- class: ["form-group", isHoriz(fStyle) && "row"],
69
- ...(hdr.showIf && {
70
- "data-show-if": mkShowIf(hdr.showIf),
71
- }),
72
- },
73
- hdr.input_type === "section_header"
74
- ? div(
75
- { class: `col-sm-12` },
76
- h5(text(hdr.label)),
77
- hdr.sublabel && p(i(hdr.sublabel))
78
- )
79
- : [
80
- div(
81
- { class: isHoriz(fStyle) && `col-sm-${labelCols}` },
82
- label(
83
- {
84
- for: `input${text_attr(hdr.form_name)}`,
85
- },
86
- text(hdr.label)
87
- )
88
- ),
89
- div(
90
- { class: isHoriz(fStyle) && `col-sm-${12 - labelCols}` },
91
- inner,
92
- text(error),
93
- hdr.sublabel && i(text(hdr.sublabel))
94
- ),
95
- ]
96
- );
97
-
98
- /**
99
- * @param {object[]} v
100
- * @param {object[]} errors
101
- * @param {string} [nameAdd = ""]
102
- * @returns {function}
103
- */
104
- const innerField = (v, errors, nameAdd = "") => (hdr) => {
105
- const name = hdr.form_name + nameAdd;
106
- const validClass = errors[name] ? "is-invalid" : "";
107
- const maybe_disabled = hdr.disabled ? " disabled" : "";
108
- switch (hdr.input_type) {
109
- case "fromtype":
110
- return displayEdit(
111
- hdr,
112
- name,
113
- v && isdef(v[hdr.form_name]) ? v[hdr.form_name] : hdr.default,
114
- validClass
115
- );
116
- case "hidden":
117
- return `<input type="hidden" class="form-control ${validClass} ${
118
- hdr.class || ""
119
- }" name="${text_attr(name)}" ${
120
- v ? `value="${text_attr(v[hdr.form_name])}"` : ""
121
- }>`;
122
- case "select":
123
- const opts = select_options(v, hdr);
124
- return `<select class="form-control ${validClass} ${
125
- hdr.class || ""
126
- }"${maybe_disabled} data-fieldname="${text_attr(
127
- hdr.form_name
128
- )}" name="${text_attr(name)}" id="input${text_attr(name)}"${
129
- hdr.attributes && hdr.attributes.explainers
130
- ? ` data-explainers="${encodeURIComponent(
131
- JSON.stringify(hdr.attributes.explainers)
132
- )}"`
133
- : ""
134
- }>${opts}</select>`;
135
- case "textarea":
136
- return `<textarea class="form-control ${validClass} ${
137
- hdr.class || ""
138
- }"${maybe_disabled} data-fieldname="${text_attr(
139
- hdr.form_name
140
- )}" name="${text_attr(name)}" id="input${text_attr(name)}">${text(
141
- v[hdr.form_name] || ""
142
- )}</textarea>`;
143
- case "code":
144
- return `<textarea mode="${
145
- (hdr.attributes || {}).mode || ""
146
- }" class="to-code form-control ${validClass} ${
147
- hdr.class || ""
148
- }"${maybe_disabled} data-fieldname="${text_attr(
149
- hdr.form_name
150
- )}" name="${text_attr(name)}" id="input${text_attr(name)}">${
151
- v[hdr.form_name] || ""
152
- }</textarea>`;
153
- case "file":
154
- if (hdr.attributes && hdr.attributes.select_file_where) {
155
- hdr.input_type = "select";
156
- return innerField(v, errors, nameAdd)(hdr);
157
- } else
158
- return `${
159
- v[hdr.form_name] ? text(v[hdr.form_name]) : ""
160
- }<input type="file" class="form-control-file ${validClass} ${
161
- hdr.class || ""
162
- }"${maybe_disabled} name="${text_attr(name)}" id="input${text_attr(
163
- name
164
- )}">`;
165
- case "search":
166
- return search_bar(name, v && v[hdr.form_name]);
167
- case "section_header":
168
- return "";
169
- case "custom_html":
170
- return hdr.attributes.html;
171
- default:
172
- const the_input = `<input type="${
173
- hdr.input_type
174
- }" class="form-control ${validClass} ${
175
- hdr.class || ""
176
- }"${maybe_disabled} data-fieldname="${text_attr(
177
- hdr.form_name
178
- )}" name="${name}" id="input${text_attr(name)}"${
179
- v && isdef(v[hdr.form_name])
180
- ? ` value="${text_attr(v[hdr.form_name])}"`
181
- : ""
182
- }>`;
183
- const inner = hdr.postText
184
- ? div(
185
- { class: "input-group" },
186
- the_input,
187
- div(
188
- { class: "input-group-append" },
189
- span(
190
- { class: "input-group-text", id: "basic-addon2" },
191
- hdr.postText
192
- )
193
- )
194
- )
195
- : the_input;
196
- return inner;
197
- }
198
- };
199
-
200
- /**
201
- * @param {object[]} v
202
- * @param {object[]} errors
203
- * @param {string} formStyle
204
- * @param {object[]} labelCols
205
- * @returns {function}
206
- */
207
- const mkFormRow = (v, errors, formStyle, labelCols) => (hdr) =>
208
- hdr.isRepeat
209
- ? mkFormRowForRepeat(v, errors, formStyle, labelCols, hdr)
210
- : mkFormRowForField(v, errors, formStyle, labelCols)(hdr);
211
-
212
- /**
213
- * @param {object[]} v
214
- * @param {object[]} errors
215
- * @param {string} formStyle
216
- * @param {object[]} labelCols
217
- * @param {object} hdr
218
- * @returns {div}
219
- */
220
- const mkFormRowForRepeat = (v, errors, formStyle, labelCols, hdr) => {
221
- const adder = a(
222
- {
223
- class: "btn btn-sm btn-outline-primary mb-3",
224
- href: `javascript:add_repeater('${hdr.form_name}')`,
225
- title: "Add",
226
- },
227
- i({ class: "fas fa-plus" })
228
- );
229
- const icons = div(
230
- { class: "float-right" },
231
- span(
232
- { onclick: "rep_up(this)" },
233
- i({ class: "fa fa-arrow-up pull-right" })
234
- ),
235
- "&nbsp;",
236
- span({ onclick: "rep_del(this)" }, i({ class: "fa fa-times pull-right" })),
237
- "&nbsp;",
238
- span(
239
- { onclick: "rep_down(this)" },
240
- i({ class: "fa fa-arrow-down pull-right" })
241
- )
242
- );
243
- if (Array.isArray(v[hdr.form_name]) && v[hdr.form_name].length > 0) {
244
- return (
245
- div(
246
- { class: `repeats-${hdr.form_name}` },
247
- v[hdr.form_name].map((vi, ix) => {
248
- return div(
249
- { class: `form-repeat form-namespace repeat-${hdr.form_name}` },
250
- icons,
251
- hdr.fields.map((f) => {
252
- return mkFormRowForField(
253
- vi,
254
- errors,
255
- formStyle,
256
- labelCols,
257
- "_" + ix
258
- )(f);
259
- })
260
- );
261
- })
262
- ) + adder
263
- );
264
- } else {
265
- return (
266
- div(
267
- { class: `repeats-${hdr.form_name}` },
268
- div(
269
- { class: `form-repeat form-namespace repeat-${hdr.form_name}` },
270
- icons,
271
- hdr.fields.map((f) => {
272
- return mkFormRowForField(v, errors, formStyle, labelCols, "_0")(f);
273
- })
274
- )
275
- ) + adder
276
- );
277
- }
278
- };
279
-
280
- /**
281
- * @param {object} hdr
282
- * @param {string} name
283
- * @param {object} v
284
- * @param {string} extracls
285
- * @returns {*}
286
- */
287
- const displayEdit = (hdr, name, v, extracls) => {
288
- var fieldview;
289
- var attributes = hdr.attributes;
290
- if (hdr.disabled) attributes.disabled = true;
291
- if (hdr.fieldviewObj) {
292
- fieldview = hdr.fieldviewObj;
293
- } else if (hdr.fieldview && hdr.type && hdr.type.fieldviews[hdr.fieldview])
294
- fieldview = hdr.type.fieldviews[hdr.fieldview];
295
- else if (hdr.type && hdr.type.fieldviews) {
296
- //first isedit fieldview
297
- fieldview = Object.entries(hdr.type.fieldviews).find(
298
- ([nm, fv]) => fv.isEdit
299
- )[1];
300
- }
301
- if (!fieldview) {
302
- if (!hdr.type)
303
- throw new Error(`Unknown type ${hdr.typename} in field ${name}`);
304
- else throw new Error(`Cannot find fieldview for field ${name}`);
305
- }
306
- if (fieldview.isEdit)
307
- return fieldview.run(
308
- name,
309
- v,
310
- attributes,
311
- extracls + " " + hdr.class,
312
- hdr.required,
313
- hdr
314
- );
315
- else return fieldview.run(v, undefined, attributes);
316
- };
317
-
318
- /**
319
- * @param {object[]} v
320
- * @param {object[]} errors
321
- * @param {string} formStyle
322
- * @param {string} labelCols
323
- * @param {string} [nameAdd = ""]
324
- * @returns {function}
325
- */
326
- const mkFormRowForField = (v, errors, formStyle, labelCols, nameAdd = "") => (
327
- hdr
328
- ) => {
329
- const name = hdr.form_name + nameAdd;
330
- const errorFeedback = errors[name]
331
- ? `<div class="invalid-feedback">${text(errors[name])}</div>`
332
- : "";
333
- if (hdr.input_type === "hidden") {
334
- return innerField(v, errors, nameAdd)(hdr);
335
- } else
336
- return formRowWrap(
337
- hdr,
338
- innerField(v, errors, nameAdd)(hdr),
339
- errorFeedback,
340
- formStyle,
341
- labelCols
342
- );
343
- };
344
-
345
- /**
346
- * @param {object} form
347
- * @returns {string}
348
- */
349
- const renderFormLayout = (form) => {
350
- const blockDispatch = {
351
- field(segment) {
352
- const field = form.fields.find((f) => f.name === segment.field_name);
353
- if (field && field.input_type !== "hidden") {
354
- if (field.sourceURL) return div({ "data-source-url": field.sourceURL });
355
-
356
- const errorFeedback = form.errors[field.name]
357
- ? `<div class="invalid-feedback">${text(
358
- form.errors[field.name]
359
- )}</div>`
360
- : "";
361
- field.attributes = { ...field.attributes, ...segment.configuration };
362
- return innerField(form.values, form.errors)(field) + errorFeedback;
363
- } else return "";
364
- },
365
- action({
366
- action_name,
367
- action_label,
368
- action_url,
369
- confirm,
370
- action_style,
371
- action_size,
372
- action_icon,
373
- configuration,
374
- action_bgcol,
375
- action_bordercol,
376
- action_textcol,
377
- }) {
378
- let style =
379
- action_style === "btn-custom-color"
380
- ? `background-color: ${action_bgcol || "#000000"};border-color: ${
381
- action_bordercol || "#000000"
382
- }; color: ${action_textcol || "#000000"}`
383
- : null;
384
- if (action_name && action_name.startsWith("Login with ")) {
385
- const method_label = action_name.replace("Login with ", "");
386
-
387
- return a(
388
- {
389
- href: `/auth/login-with/${method_label}`,
390
- //TODO get url through form.req to reduce coupling
391
- class: [
392
- action_style !== "btn-link" &&
393
- `btn ${action_style || "btn-primary"} ${action_size || ""}`,
394
- ],
395
- style,
396
- },
397
- action_icon ? i({ class: action_icon }) + "&nbsp;" : false,
398
- action_label || action_name
399
- );
400
- }
401
- const mkBtn = (onclick_or_type) =>
402
- `<button ${onclick_or_type} class="${
403
- action_style === "btn-link"
404
- ? ""
405
- : `btn ${action_style || "btn-primary"} ${action_size || ""}`
406
- }"${style ? ` style="${style}"` : ""}>${
407
- action_icon ? `<i class="${action_icon}"></i>&nbsp;` : ""
408
- }${text(
409
- action_label || form.submitLabel || action_name || "Save"
410
- )}</button>`;
411
-
412
- if (action_name === "Delete") {
413
- if (action_url) {
414
- const dest = (configuration && configuration.after_delete_url) || "/";
415
- return mkBtn(
416
- `onClick="ajax_post('${action_url}', {success:()=>window.location.href='${dest}'})" type="button"`
417
- );
418
- } else return "";
419
- }
420
- const submitAttr = form.xhrSubmit
421
- ? 'onClick="ajaxSubmitForm(this)" type="button"'
422
- : 'type="submit"';
423
- return mkBtn(submitAttr);
424
- },
425
- };
426
- return renderLayout({ blockDispatch, layout: form.layout });
427
- };
428
-
429
- /**
430
- * @param {string|object} form
431
- * @param {string|false} csrfToken0
432
- * @returns {string}
433
- */
434
- const renderForm = (form, csrfToken0) => {
435
- const csrfToken =
436
- csrfToken0 === false || csrfToken0 === ""
437
- ? csrfToken0
438
- : csrfToken0 || (form.req && form.req.csrfToken && form.req.csrfToken());
439
-
440
- if (typeof form === "string") return form;
441
- if (form.isStateForm) {
442
- form.class += " px-4 py-3";
443
- form.formStyle = "vert";
444
- var collapsedSummary = "";
445
- Object.entries(form.values).forEach(([k, v]) => {
446
- if (typeof v === "undefined") return;
447
- if (k[0] !== "_") collapsedSummary += ` ${text(k)}:${text_attr(v)} `;
448
- if (k === "_fts") collapsedSummary += ` ${text_attr(v)} `;
449
- });
450
- return div(
451
- { class: "dropdown" },
452
- button(
453
- {
454
- class: "btn btn-secondary dropdown-toggle",
455
- type: "button",
456
- id: "dropdownMenuButton",
457
- "data-toggle": "dropdown",
458
- "aria-haspopup": "true",
459
- "aria-expanded": "false",
460
- },
461
- collapsedSummary ||
462
- (form.__ ? form.__("Search filter") : "Search filter")
463
- ),
464
-
465
- div(
466
- {
467
- class: "dropdown-menu search-form",
468
- "aria-labelledby": "dropdownMenuButton",
469
- },
470
- mkForm(form, csrfToken, form.errors)
471
- )
472
- );
473
- } else if (form.layout) return mkFormWithLayout(form, csrfToken);
474
- else return mkForm(form, csrfToken, form.errors);
475
- };
476
-
477
- /**
478
- * @param {object} form
479
- * @param {string} csrfToken
480
- * @returns {string}
481
- */
482
- const mkFormWithLayout = (form, csrfToken) => {
483
- const hasFile = form.fields.some((f) => f.input_type === "file");
484
- const csrfField = `<input type="hidden" name="_csrf" value="${csrfToken}">`;
485
- const top = `<form action="${form.action}"${
486
- form.onChange ? ` onchange="${form.onChange}"` : ""
487
- } class="form-namespace ${form.class || ""}" method="${
488
- form.methodGET ? "get" : "post"
489
- }"${hasFile ? ' encType="multipart/form-data"' : ""}>`;
490
- const blurbp = form.blurb
491
- ? Array.isArray(form.blurb)
492
- ? form.blurb.join("")
493
- : p(text(form.blurb))
494
- : "";
495
- const hiddens = form.fields
496
- .filter((f) => f.input_type === "hidden")
497
- .map((f) => innerField(form.values, form.errors)(f))
498
- .join("");
499
- const fullFormError = form.errors._form
500
- ? `<div class="form-group row">
501
- <div class="col-sm-12">
502
- <p class="text-danger">${form.errors._form}
503
- </p>
504
- </div>
505
- </div>`
506
- : "";
507
- return (
508
- blurbp +
509
- top +
510
- csrfField +
511
- hiddens +
512
- renderFormLayout(form) +
513
- fullFormError +
514
- "</form>"
515
- );
516
- };
517
-
518
- /**
519
- * @param {object[]} additionalButtons
520
- * @returns {string}
521
- */
522
- const displayAdditionalButtons = (additionalButtons) =>
523
- additionalButtons
524
- .map(
525
- (btn) =>
526
- `<button type="button" id="${btn.id}" class="${btn.class}"${
527
- btn.onclick ? ` onclick="${btn.onclick}"` : ""
528
- }>${btn.label}</button>&nbsp;`
529
- )
530
- .join("");
531
-
532
- /**
533
- * @param {object} form
534
- * @param {string} csrfToken
535
- * @param {object} [errors = {}]
536
- * @returns {string}
537
- */
538
- const mkForm = (form, csrfToken, errors = {}) => {
539
- const hasFile = form.fields.some((f) => f.input_type === "file");
540
- const csrfField =
541
- csrfToken === false
542
- ? ""
543
- : `<input type="hidden" name="_csrf" value="${csrfToken}">`;
544
- const top = `<form ${form.id ? `id="${form.id}" ` : ""}action="${
545
- form.action
546
- }" ${
547
- form.onChange ? ` onchange="${form.onChange}"` : ""
548
- }class="form-namespace ${form.isStateForm ? "stateForm" : ""} ${
549
- form.class || ""
550
- }" method="${form.methodGET ? "get" : "post"}"${
551
- hasFile ? ' encType="multipart/form-data"' : ""
552
- }>`;
553
- //console.log(form.fields);
554
- const flds = form.fields
555
- .map(
556
- mkFormRow(
557
- form.values,
558
- errors,
559
- form.formStyle,
560
- typeof form.labelCols === "undefined" ? 2 : form.labelCols
561
- )
562
- )
563
- .join("");
564
- const blurbp = form.blurb
565
- ? Array.isArray(form.blurb)
566
- ? form.blurb.join("")
567
- : p(text(form.blurb))
568
- : "";
569
- const fullFormError = errors._form
570
- ? `<div class="form-group row">
571
- <div class="col-sm-12">
572
- <p class="text-danger">${errors._form}
573
- </p>
574
- </div>
575
- </div>`
576
- : "";
577
- const bot = `<div class="form-group row">
578
- <div class="col-sm-12">
579
- ${
580
- form.additionalButtons
581
- ? displayAdditionalButtons(form.additionalButtons)
582
- : ""
583
- }
584
- ${
585
- form.noSubmitButton
586
- ? ""
587
- : `<button type="submit" class="btn ${
588
- form.submitButtonClass || "btn-primary"
589
- }">${text(form.submitLabel || "Save")}</button>`
590
- }
591
- </div>
592
- </div>`;
593
- return blurbp + top + csrfField + flds + fullFormError + bot + "</form>";
594
- };
595
-
596
- module.exports =
597
- contract(
598
- is.fun(
599
- [is.or(is.str, is.class("Form")), is.maybe(is.or(is.str, is.eq(false)))],
600
- is.str
601
- ),
602
- renderForm
603
- );