Liquid
Use the logical name, prefixed with
cr34c_for accessing data!
Code Snippets
Button to a Page
<a href="/OpportunityApply?id={{ opportunityId }}" class="btn button1">Button</a>
{% assign opportunityId = request.params.id %}
{% if opportunityId %}
{% assign opportunity = entities['cr34c_opportunity'][opportunityId] %}
{% if opportunity %}
<div class="opportunity-details">
<div class="opportunity-header">
<h1>{{ opportunity.cr34c_name }}</h1>
</div>
<div class="opportunity-card">
<div class="detail-row">
<div class="detail-label">Description</div>
<div class="detail-value">
{{ opportunity.cr34c_description | default: 'No description provided.' }}
</div>
</div>
<div class="detail-row">
<div class="detail-label">Location</div>
<div class="detail-value">
{{ opportunity.cr34c_location | default: 'No location provided.' }}
</div>
</div>
</div>
</div>
{% else %}
<div class="alert alert-danger">
Opportunity not found.
</div>
{% endif %}
{% else %}
<div class="alert alert-warning">
No opportunity was specified.
</div>
{% endif %}
{% for opportunity in opportunities.results.entities %}
<div class="opportunity-row">
<input
type="checkbox"
class="row-select"
data-record-id="{{ opportunity.cr34c_opportunityid }}"
data-record-name="{{ opportunity.cr34c_name | escape }}"
>
<span>{{ opportunity.cr34c_name }}</span>
</div>
{% endfor %}
<button
type="button"
id="bulk-download-btn"
class="btn btn-primary">
Download Selected ZIP
</button>
$(document).ready(function () {
$("#bulk-download-btn").on("click", async function () {
const selectedRows = $(".row-select:checked");
if (selectedRows.length === 0) {
alert("Please select at least one row.");
return;
}
const button = $(this);
button.prop("disabled", true)
.text("Preparing ZIP...");
try {
const zip = new JSZip();
for (const checkbox of selectedRows) {
const recordId = $(checkbox).data("record-id");
const recordName = $(checkbox).data("record-name");
const folderName = sanitizeFileName(
recordName || recordId
);
const folder = zip.folder(folderName);
console.log("Processing:", recordName);
// Retrieve notes associated with this record
const annotations = await getAnnotations(recordId);
for (const note of annotations) {
if (!note.isdocument || !note.documentbody) {
continue;
}
const fileName = sanitizeFileName(
note.filename || note.annotationid
);
const fileBytes = base64ToUint8Array(
note.documentbody
);
folder.file(fileName, fileBytes);
}
// Optional: Add the record ID to a text file
folder.file(
"record-id.txt",
recordId
);
}
const zipBlob = await zip.generateAsync({
type: "blob",
compression: "DEFLATE",
compressionOptions: {
level: 6
}
});
const downloadUrl = URL.createObjectURL(zipBlob);
const link = document.createElement("a");
link.href = downloadUrl;
link.download = "selected-records.zip";
document.body.appendChild(link);
link.click();
link.remove();
URL.revokeObjectURL(downloadUrl);
} catch (error) {
console.error("ZIP download failed:", error);
alert("Unable to create the ZIP file.");
} finally {
button.prop("disabled", false)
.text("Download Selected ZIP");
}
});
// Retrieve annotations for a parent record
async function getAnnotations(recordId) {
const cleanId = recordId.replace(/[{}]/g, "");
const query =
"?$select=annotationid,filename,mimetype," +
"documentbody,isdocument" +
"&$filter=_objectid_value eq " +
cleanId +
" and isdocument eq true";
const response = await webapi.safeAjax({
type: "GET",
url: "/_api/annotations" + query,
contentType: "application/json"
});
return response.value || [];
}
// Convert Base64 to binary bytes
function base64ToUint8Array(base64) {
const binaryString = atob(base64);
const bytes = new Uint8Array(
binaryString.length
);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
}
// Sanitize file/folder names
function sanitizeFileName(name) {
return String(name)
.replace(/[<>:"/\\|?*\x00-\x1F]/g, "_")
.trim()
.substring(0, 150) || "Unnamed";
}
});function getAnnotations(recordId) {
return new Promise(function (resolve, reject) {
const cleanId = recordId.replace(/[{}]/g, "");
const query =
"?$select=annotationid,filename,mimetype," +
"documentbody,isdocument" +
"&$filter=_objectid_value eq " +
cleanId +
" and isdocument eq true";
webapi.safeAjax({
type: "GET",
url: "/_api/annotations" + query,
contentType: "application/json",
success: function (data) {
resolve(data.value || []);
},
error: function (xhr) {
reject(xhr);
}
});
});
}
https://github.com/MicrosoftDocs/power-pages-docs/blob/main/power-pages-docs/configure/webapi-azure-blob.md
https://learn.microsoft.com/en-us/power-apps/developer/data-platform/attachment-annotation-files?tabs=sdk