diff --git a/docs/REPORT BUILDER/reports-examples/_order.yaml b/docs/REPORT BUILDER/reports-examples/_order.yaml
index b4ef2010..19869665 100644
--- a/docs/REPORT BUILDER/reports-examples/_order.yaml
+++ b/docs/REPORT BUILDER/reports-examples/_order.yaml
@@ -15,6 +15,7 @@
- repeatable-field-child-records
- resize
- signature
+- sketches
- section
- modifying-the-widthheight-of-the-table
- timezone
diff --git a/docs/REPORT BUILDER/reports-examples/sketches.md b/docs/REPORT BUILDER/reports-examples/sketches.md
new file mode 100644
index 00000000..cebce1bc
--- /dev/null
+++ b/docs/REPORT BUILDER/reports-examples/sketches.md
@@ -0,0 +1,60 @@
+---
+title: Sketches
+excerpt: ""
+deprecated: false
+hidden: false
+metadata:
+ title: ""
+ description: ""
+ robots: noindex
+next:
+ description: ""
+---
+
+## Display Sketches in Reports
+
+In the BODY section, search for `element.isSketchElement`. If it doesn't exist, you can add it to your `RENDERVALUES` loop.
+
+```javascript
+<% } else if (element.isSketchElement) { %>
+
+
<%= element.label %>
+
+ <% value && value.items.forEach((item, index) => { %>
+
 %>)
+ <% if (item.caption) { %>
+
<%= item.caption %>
+ <% } %> <% }); %>
+
+
+<% } %>
+```
+
+## Resize Sketches
+
+You can control the size of sketches in the STYLES section by targeting the `.sketch` class.
+
+```css
+.sketch {
+ max-width: 100%;
+ height: auto;
+}
+```
+
+## Accessing a specific sketch by its field name
+
+If you want to display the first sketch from a specific field:
+
+```javascript
+
+ <%
+ var sketchField = record.formValues.find('my_sketch_field');
+ var sketchId = sketchField && sketchField.items[0] && sketchField.items[0].mediaID;
+ %>
+ <% if (sketchId) { %>
+
+
 %>)
+
+ <% } %>
+
+```
diff --git a/docs/REPORT BUILDER/reports-introduction/functions.md b/docs/REPORT BUILDER/reports-introduction/functions.md
index 39afe8e6..9dc4a587 100644
--- a/docs/REPORT BUILDER/reports-introduction/functions.md
+++ b/docs/REPORT BUILDER/reports-introduction/functions.md
@@ -1,15 +1,16 @@
---
title: Functions
-excerpt: ''
+excerpt: ""
deprecated: false
hidden: false
metadata:
- title: ''
- description: ''
+ title: ""
+ description: ""
robots: index
next:
- description: ''
+ description: ""
---
+
## API
Make a Fulcrum [REST API](https://fulcrum.readme.io/reference) call
@@ -27,10 +28,10 @@ String
### Examples
```js
-API('/choice_lists', {qs: {per_page: 1}})
+API("/choice_lists", { qs: { per_page: 1 } });
```
-***
+---
## AUDIOURL
@@ -49,10 +50,10 @@ String
### Examples
```js
-AUDIOURL($my_audio_field[0].audio_id, {version: 'original'})
+AUDIOURL($my_audio_field[0].audio_id, { version: "original" });
```
-***
+---
## FORMATDATE
@@ -71,10 +72,10 @@ String
### Examples
```js
-FORMATDATE(new Date())
+FORMATDATE(new Date());
```
-***
+---
## GET
@@ -93,10 +94,10 @@ String
### Examples
```js
-GET('https://jsonplaceholder.typicode.com/posts', {qs: {userId: 1}})
+GET("https://jsonplaceholder.typicode.com/posts", { qs: { userId: 1 } });
```
-***
+---
## GETBLOB
@@ -115,10 +116,10 @@ String
### Examples
```js
-GETBLOB('https://learn.fulcrumapp.com/img/branding/fulcrum-icon.png')
+GETBLOB("https://learn.fulcrumapp.com/img/branding/fulcrum-icon.png");
```
-***
+---
## JSONREQUEST
@@ -135,10 +136,13 @@ String
### Examples
```js
-JSONREQUEST({url: 'https://jsonplaceholder.typicode.com/posts', qs: {userId: 1}})
+JSONREQUEST({
+ url: "https://jsonplaceholder.typicode.com/posts",
+ qs: { userId: 1 },
+});
```
-***
+---
## LOG
@@ -155,10 +159,10 @@ String
### Examples
```js
-LOG('Hello World')
+LOG("Hello World");
```
-***
+---
## PHOTOURL
@@ -177,10 +181,10 @@ String
### Examples
```js
-PHOTOURL($my_photo_field[0].photo_id, {version: 'thumb'})
+PHOTOURL($my_photo_field[0].photo_id, { version: "thumb" });
```
-***
+---
## QS
@@ -202,7 +206,7 @@ QS({name: "Robert", age: "20"}
// name=Robert&age=20
```
-***
+---
## QUERY
@@ -221,10 +225,10 @@ String
### Examples
```js
-QUERY('SELECT name FROM forms', {format: 'json'})
+QUERY("SELECT name FROM forms", { format: "json" });
```
-***
+---
## QUERYVALUE
@@ -241,10 +245,10 @@ String
### Examples
```js
-QUERYVALUE(`SELECT form_id FROM forms WHERE name = '${form.name}'`)
+QUERYVALUE(`SELECT form_id FROM forms WHERE name = '${form.name}'`);
```
-***
+---
## RENDER
@@ -252,7 +256,7 @@ The RENDER function is created automatically in all new advanced report template
### Parameters
-`feature` Record or RepeatableItemValue (**required**) - The feature you are looking to render.
+`feature` Record or RepeatableItemValue (**required**) - The feature you are looking to render.
`options` System level variable that does not need to be defined.
@@ -263,7 +267,12 @@ The RENDER function is created automatically in all new advanced report template
### Function Definition
```javascript
-const RENDER = (feature, options, eachFunction, {container, parent, index, allValues} = {}) => {
+const RENDER = (
+ feature,
+ options,
+ eachFunction,
+ { container, parent, index, allValues } = {},
+) => {
if (!container) {
container = feature.formValues.container;
}
@@ -275,40 +284,74 @@ const RENDER = (feature, options, eachFunction, {container, parent, index, allVa
for (const element of container.elements) {
const formValue = feature.formValues.get(element.key);
- const renderSection = element.isSectionElement ? () => {
- global.RENDER(feature, options, eachFunction, {container: element, parent, feature, index, allValues});
- } : null;
-
- const renderRepeatableItems = element.isRepeatableElement ? (eachItemFunction) => {
- if (!formValue) {
- return;
- }
-
- for (let i = 0; i < formValue.items.length; ++i) {
- const item = formValue.items[i];
-
- const newAllValues = allValues.copy();
-
- newAllValues.merge(item.formValues);
- newAllValues.merge(feature.formValues); // Add parent values too
-
- const renderItem = () => {
- global.RENDER(item, options, eachFunction, {container: element, parent: feature, feature: item, index: i, allValues: newAllValues});
- };
-
- eachItemFunction({element, value: item, renderItem, container: element, parent: feature, feature: item, index: i, allValues: newAllValues});
- }
- } : null;
+ const renderSection = element.isSectionElement
+ ? () => {
+ global.RENDER(feature, options, eachFunction, {
+ container: element,
+ parent,
+ feature,
+ index,
+ allValues,
+ });
+ }
+ : null;
+
+ const renderRepeatableItems = element.isRepeatableElement
+ ? (eachItemFunction) => {
+ if (!formValue) {
+ return;
+ }
+
+ for (let i = 0; i < formValue.items.length; ++i) {
+ const item = formValue.items[i];
+
+ const newAllValues = allValues.copy();
+
+ newAllValues.merge(item.formValues);
+ newAllValues.merge(feature.formValues); // Add parent values too
+
+ const renderItem = () => {
+ global.RENDER(item, options, eachFunction, {
+ container: element,
+ parent: feature,
+ feature: item,
+ index: i,
+ allValues: newAllValues,
+ });
+ };
+
+ eachItemFunction({
+ element,
+ value: item,
+ renderItem,
+ container: element,
+ parent: feature,
+ feature: item,
+ index: i,
+ allValues: newAllValues,
+ });
+ }
+ }
+ : null;
if (eachFunction) {
- eachFunction({element, value: formValue, renderSection, renderRepeatableItems, container, feature, index, parent, allValues});
+ eachFunction({
+ element,
+ value: formValue,
+ renderSection,
+ renderRepeatableItems,
+ container,
+ feature,
+ index,
+ parent,
+ allValues,
+ });
}
}
};
-
```
-***
+---
@@ -330,58 +373,69 @@ JSON - the feature `elements` and `values`
### Examples
-```html
-<% RENDERVALUES(record, null, function(element, value) { %>
- <% if (element.isSectionElement) { %>
- <%= element.label %>
- <% } else if (element.isRepeatableElement) { %>
- <% if (value.length) { %>
- <%= element.label %> <%= value && `(${value.displayValue})` %>
- <% } else { %>
- <%= value && value.displayValue %>
- <% } %>
- <% } else if (element.isPhotoElement) { %>
-
-
<%= element.label %>
-
- <% value && value.items.forEach((item, index) => { %>
-
 %>)
- <% if (item.caption) { %>
-
<%= item.caption %>
- <% } %>
- <% }); %>
-
-
- <% } else if (element.isSignatureElement) { %>
-
-
<%= element.label %>
- <% if (value && !value.isEmpty) { %>
-
-
 %>)
- <% if (value.timestamp) { %>
-
<%= element.agreementText %>
-
Signed <%= FORMATDATE(value.timestamp) %>
- <% } %>
-
- <% } %>
-
- <% } else if (element.isRecordLinkElement) { %>
-
-
<%= element.label %>
- <% if (value && !value.isEmpty) { %>
-
<%= value.items.map(item => item.displayValue).join(', ') %>
- <% } %>
-
- <% } else { %>
-
-
<%= element.label %>
-
<%= value && value.displayValue %>
-
+<% RENDERVALUES(record, null, function(element, value) { %> <% if
+(element.isSectionElement) { %>
+
+<%= element.label %>
+<% } else if (element.isRepeatableElement) { %> <% if (value.length) { %>
+
+ <%= element.label %> <%= value && `(${value.displayValue})` %>
+
+<% } else { %>
+<%= value && value.displayValue %>
+<% } %> <% } else if (element.isPhotoElement) { %>
+
+
<%= element.label %>
+
+ <% value && value.items.forEach((item, index) => { %>
+
 %>)
+ <% if (item.caption) { %>
+
<%= item.caption %>
+ <% } %> <% }); %>
+
+
+<% } else if (element.isSketchElement) { %>
+
+
<%= element.label %>
+
+ <% value && value.items.forEach((item, index) => { %>
+
 %>)
+ <% if (item.caption) { %>
+
<%= item.caption %>
+ <% } %> <% }); %>
+
+
+<% } else if (element.isSignatureElement) { %>
+
+
<%= element.label %>
+ <% if (value && !value.isEmpty) { %>
+
+
 %>)
+ <% if (value.timestamp) { %>
+
<%= element.agreementText %>
+
Signed <%= FORMATDATE(value.timestamp) %>
+ <% } %>
+
<% } %>
-<% }) %>
+
+<% } else if (element.isRecordLinkElement) { %>
+
+
<%= element.label %>
+ <% if (value && !value.isEmpty) { %>
+
+ <%= value.items.map(item => item.displayValue).join(', ') %>
+
+ <% } %>
+
+<% } else { %>
+
+
<%= element.label %>
+
<%= value && value.displayValue %>
+
+<% } %> <% }) %>
```
-***
+---
@@ -402,10 +456,34 @@ String
### Examples
```js
-SIGNATUREURL($my_signature_field.signature_id, {version: 'original'})
+SIGNATUREURL($my_signature_field.signature_id, { version: "original" });
```
-***
+---
+
+
+
+## SKETCHURL
+
+Generate a public sketch URL
+
+### Parameters
+
+`id` String (**required**) - The ID of the sketch
+
+`options` Object - `{version: 'large', expires: null}`
+
+### Returns
+
+String
+
+### Examples
+
+```js
+SKETCHURL($my_sketch_field[0].sketch_id, { version: "large" });
+```
+
+---
## STATICMAP
@@ -430,14 +508,16 @@ STATICMAP({mapEngine: ‘google’,markers: '34.052230,-118.243680', maptype: 'h
```
```js Esri
-
+
```
```html
-
+
```
-***
+---
## TOJSON
@@ -454,10 +534,10 @@ String
### Examples
```js
-TOJSON(API('/choice_lists').choice_lists[0].name)
+TOJSON(API("/choice_lists").choice_lists[0].name);
```
-***
+---
## VIDEOURL
@@ -476,5 +556,5 @@ String
### Examples
```js
-VIDEOURL($my_video_field[0].video_id, {version: 'original'})
+VIDEOURL($my_video_field[0].video_id, { version: "original" });
```
diff --git a/reference/ATTACHMENTS/_order.yaml b/reference/ATTACHMENTS/_order.yaml
index bbe2c045..72739aae 100644
--- a/reference/ATTACHMENTS/_order.yaml
+++ b/reference/ATTACHMENTS/_order.yaml
@@ -3,4 +3,5 @@
- get-single-attachment
- create-attachment
- finalize-attachment
+- copy-all-attachments
- delete-attachment
diff --git a/reference/ATTACHMENTS/copy-all-attachments.md b/reference/ATTACHMENTS/copy-all-attachments.md
new file mode 100644
index 00000000..20e1d6b5
--- /dev/null
+++ b/reference/ATTACHMENTS/copy-all-attachments.md
@@ -0,0 +1,23 @@
+---
+title: Copy All Reference Files
+excerpt: >-
+ Copy all reference files from one form to another.
+api:
+ file: rest-api.json
+ operationId: copy-all-attachments
+deprecated: false
+hidden: false
+metadata:
+ title: ""
+ description: ""
+ robots: noindex
+next:
+ description: ""
+---
+
+This endpoint allows you to copy all reference files from a source form to a destination form.
+
+# Limits
+
+- **100 reference files** per form.
+- **1GB** total reference file size per form.
diff --git a/reference/rest-api.json b/reference/rest-api.json
index 0a342280..c4930255 100644
--- a/reference/rest-api.json
+++ b/reference/rest-api.json
@@ -511,6 +511,43 @@
},
"description": "Location metadata captured at creation or update time."
},
+ "AttachmentCopyAllRequest": {
+ "type": "object",
+ "required": [
+ "source_parent_id",
+ "destination_parent_id",
+ "parent_type"
+ ],
+ "properties": {
+ "source_parent_id": {
+ "type": "string",
+ "description": "The ID of the source parent (e.g. form ID) to copy reference files from"
+ },
+ "destination_parent_id": {
+ "type": "string",
+ "description": "The ID of the destination parent (e.g. form ID) to copy reference files to"
+ },
+ "parent_type": {
+ "type": "string",
+ "description": "The type of the parent resource",
+ "enum": [
+ "form"
+ ]
+ }
+ }
+ },
+ "AttachmentCopyAllResponse": {
+ "type": "object",
+ "properties": {
+ "ref_file_mapping": {
+ "type": "object",
+ "description": "A mapping of original reference file attachment IDs to their new copies",
+ "additionalProperties": {
+ "type": "string"
+ }
+ }
+ }
+ },
"AttachmentCreateRequest": {
"type": "object",
"required": [
@@ -10211,6 +10248,46 @@
"deprecated": false
}
},
+ "/v2/attachments/copy_all": {
+ "post": {
+ "summary": "Copy all reference files",
+ "description": "Copy all reference files from one form to another. Limits: 100 reference files per form, 1GB total per form.",
+ "operationId": "copy-all-attachments",
+ "parameters": [
+ {
+ "name": "X-ApiToken",
+ "in": "header",
+ "description": "API Token. Required to authenticate the request.",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/AttachmentCopyAllRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/AttachmentCopyAllResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/v2/attachments/finalize": {
"post": {
"summary": "Finalize Attachment",