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