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..fc338054
--- /dev/null
+++ b/docs/REPORT BUILDER/reports-examples/sketches.md
@@ -0,0 +1,271 @@
+---
+title: Sketches
+excerpt: "Learn how to include sketches in custom reports"
+deprecated: false
+hidden: false
+metadata:
+ title: "Include Sketches in Reports"
+ description: "Complete guide to including sketches in Fulcrum custom reports with examples"
+ robots: index
+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.
+
+```html
+<% } else if (element.isSketchElement) { %>
+
+
<%= element.label %>
+
+ <% value && value.items.forEach((item, index) => { %>
+
 %>)
+ <% }); %>
+
+
+<% } %>
+```
+
+## Understanding Sketch Data Structure
+
+Sketch fields contain an array of items. Each item has the following properties:
+
+- `mediaID` - The unique identifier for the sketch
+- `isEmpty` - Boolean indicating if the field has any sketches
+
+### Accessing Sketch Data
+
+```html
+// Check if sketch field has values
+<% if ($my_sketch_field && !$my_sketch_field.isEmpty) { %>
+ <% $my_sketch_field.items.forEach((item, index) => { %>
+
+ <% }); %>
+<% } %>
+
+// Access specific sketch properties
+<% var firstSketch = $my_sketch_field && $my_sketch_field[0]; %>
+<% var sketchId = firstSketch && firstSketch.mediaID; %>
+```
+
+## SKETCHURL Function
+
+The `SKETCHURL()` function generates a public URL for a sketch. It accepts two parameters:
+
+### Parameters
+
+- `id` (String, required) - The sketch ID
+- `options` (Object, optional) - Configuration options
+ - `version` - Image version: `'large'` (default), `'original'`
+ - `expires` - Expiration timestamp (default: `null`)
+
+### Examples
+
+```html
+// Default large version
+
+
+// Original full resolution
+
+```
+
+## 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;
+}
+
+/* Fixed width */
+.sketch {
+ width: 500px;
+ height: auto;
+}
+
+/* Multiple sketches in a row */
+.sketch {
+ max-width: 48%;
+ height: auto;
+ display: inline-block;
+ margin: 1%;
+}
+```
+
+## Display Only First Sketch
+
+If you have multiple sketches attached to a field and want to display only the first one:
+
+```html
+<% } else if (element.isSketchElement) { %>
+
+
<%= element.label %>
+
+ <% if (value && value.items.length > 0) { %>
+ <% let sketchItem = value.items[0]; %>
+
 %>)
+ <% } %>
+
+
+<% } %>
+```
+
+## Accessing a Specific Sketch by Field Name
+
+If you want to display sketches from a specific field outside the `RENDERVALUES` loop:
+
+```html
+
+ <%
+ // Using the data name of your sketch field
+ var sketchField = $my_sketch_field;
+ var sketchId = sketchField && sketchField[0] && sketchField[0].mediaID;
+ %>
+ <% if (sketchId) { %>
+
+
 %>)
+
+ <% } %>
+
+
+
+
+
Site Sketches
+ <% if ($site_sketch && $site_sketch.items) { %>
+ <% $site_sketch.items.forEach((item, index) => { %>
+
+
 %>)
+
+ <% }); %>
+ <% } %>
+
+```
+
+## Add Metadata to Sketches
+
+You can query the database to add metadata like creation date to each sketch:
+
+```html
+<% } else if (element.isSketchElement) { %>
+
+
<%= element.label %>
+
+ <% value && value.items.forEach((item, index) => { %>
+ <%
+ // Query sketch metadata
+ var sketchQuery = QUERY("SELECT created_at, updated_at FROM sketches WHERE sketch_id = '" + item.mediaID + "'");
+ var sketchDate = sketchQuery.rows[0] && sketchQuery.rows[0].created_at.slice(0, 10);
+ %>
+
+
 %>)
+ <% if (sketchDate) { %>
+
Created: <%= sketchDate %>
+ <% } %>
+
+ <% }); %>
+
+
+<% } %>
+```
+
+## Hide Sketch Label When Empty
+
+Only display the sketch field label when there are sketches attached:
+
+```html
+<% } else if (element.isSketchElement) { %>
+ <% if (value && !value.isEmpty && value.items.length > 0) { %>
+
+
<%= element.label %>
+
+ <% value.items.forEach((item, index) => { %>
+
 %>)
+ <% }); %>
+
+
+ <% } %>
+<% } %>
+```
+
+## Page Break Handling
+
+To prevent sketches from breaking across pages:
+
+```html
+<% } else if (element.isSketchElement) { %>
+
+
<%= element.label %>
+
+ <% value && value.items.forEach((item, index) => { %>
+
 %>)
+ <% }); %>
+
+
+<% } %>
+```
+
+## Numbering Sketches
+
+Add numbers to sketches automatically:
+
+```html
+<% } else if (element.isSketchElement) { %>
+
+
<%= element.label %>
+
+ <% value && value.items.forEach((item, index) => { %>
+
+
Sketch <%= index + 1 %>
+
 %>)
+
+ <% }); %>
+
+
+<% } %>
+```
+
+## Grid Layout for Multiple Sketches
+
+Display sketches in a responsive grid:
+
+```css
+/* Add to STYLES section */
+.sketch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+ gap: 20px;
+ margin: 20px 0;
+}
+
+.sketch-grid-item {
+ text-align: center;
+}
+
+.sketch-grid-item img {
+ width: 100%;
+ height: auto;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ padding: 5px;
+}
+```
+
+```html
+/* Add to BODY section */
+<% } else if (element.isSketchElement) { %>
+
+
<%= element.label %>
+
+ <% value && value.items.forEach((item, index) => { %>
+
+
 %>)
+
+ <% }); %>
+
+
+<% } %>
+```
diff --git a/docs/REPORT BUILDER/reports-introduction/functions.md b/docs/REPORT BUILDER/reports-introduction/functions.md
index 39afe8e6..531e5b84 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,
+ });
}
}
};
-
```
-***
+---
@@ -331,57 +374,67 @@ 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) => { %>
+
 %>)
+ <% }); %>
+
+
+<% } 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 +455,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].mediaID, { version: "large" });
+```
+
+---
## STATICMAP
@@ -417,7 +494,7 @@ Generate a Google or Esri Static Map based on the value of the report template
To change the map engine in the STATICMAP function directly, update the options passed into the parameters of the STATICMAP function:
-`<%= STATICMAP({mapEngine: ‘esri’, markers, ...SET_MAP_OPTIONS()}) %>`
+`<%= STATICMAP({mapEngine: 'esri', markers, ...SET_MAP_OPTIONS()}) %>`
### Returns
@@ -426,18 +503,20 @@ String
### Examples
```js Google
-STATICMAP({mapEngine: ‘google’,markers: '34.052230,-118.243680', maptype: 'hybrid', size: '300x300'})
+STATICMAP({mapEngine: 'google',markers: '34.052230,-118.243680', maptype: 'hybrid', size: '300x300'})
```
```js Esri
-
+
```
```html
-
+
```
-***
+---
## TOJSON
@@ -454,10 +533,10 @@ String
### Examples
```js
-TOJSON(API('/choice_lists').choice_lists[0].name)
+TOJSON(API("/choice_lists").choice_lists[0].name);
```
-***
+---
## VIDEOURL
@@ -476,5 +555,5 @@ String
### Examples
```js
-VIDEOURL($my_video_field[0].video_id, {version: 'original'})
+VIDEOURL($my_video_field[0].video_id, { version: "original" });
```