Can anyone help w/ the render module? Via suitelet...
# suitescript
z
Can anyone help w/ the render module? Via suitelet, I can't get addCustomDataSource (JSON or stringified JSON) or addSearchResult to actually pass a value accessible by an advanced pdf template. The aliases always point to empty data. I also tried storing the data in a body field which I can source on the template via record but processing that text/string field as a collection (array) wasn't working
e
Here's the code from my cookbook on the render module showing how to use
addCustomDataSource
Oh and this is
custom-datasource.ftl
z
Thank you for providing these files. Trying to digest them and do a test. Noticing that your setup is quite a bit different using renderToResponse and a freemarker template file loaded. I was trying to do this with an 'Advanced PDF/HTML Template' and the suitelet basically loading the template, adding record and custom sources, and using .renderAsPdf
e
The usage of
addCustomDataSource
and the way you reference that data from within the template should remain the same
z
That makes sense. I still can't get the addCustomSource to work, even using the sample provided. I can source the body field, which prints a string to the form. Trying to follow this thread. Do you happen to have any advice on using/converting a text field representing an object or array within a netsuite ftl?
Including my code to be more clear
Copy code
/**
 * @NApiVersion 2.1
 * @NScriptType Suitelet
 * @since 11/29/2023
 * @author Zachary Oliver
 * @description
 */

define(['N/render', 'N/search', 'N/record', 'N/file', 'N/runtime', 'N/log', './reference_object_lib', './error_library'],
  function (render, search, record, file, runtime, log, reference_object_lib, error_library) {

    const detailedLogging = true;

    function onRequest(context) {
      try {
        const req = context.request;
        log.debug('req', req)

        const params = req.parameters;
        log.debug('params', params);

        const recordId = params.id;
        log.debug('recordId', recordId);

        let pdfFile = null;

        if (req.method === 'GET') {
          pdfFile = handleRecord(recordId);

          return context.response.writeFile({ file: pdfFile });
        }
      } catch (e) {
        let arguments = {
          record: context.newRecord,
          user: runtime.getCurrentUser(),
          script: runtime.getCurrentScript(),
          context: context
        }

        log.debug('arguments: ', arguments);

        log.debug('Error: ', e);

        log.debug('runtime.executionContext: ', runtime.executionContext);

        error_library.errorNotification(arguments, e);
      }
    }

    function handleRecord(id) {
      log.debug('handleRecord | Enter');

      const templateFile = file.load(249);

      // Create a renderer object
      const renderer = render.create();

      // Get the contents of the script template file
      renderer.templateContent = templateFile.getContents();
      log.debug('renderer.templateContent', renderer.templateContent);

      // Add the invoice record to the renderer

      if (detailedLogging) log.debug('handleRecord | after addRecord');

      const chargesResults = searchCharges(id);
      log.debug('chargesResults', chargesResults);

      const chargesJson = chargesResults[0].toJSON();
      log.debug('chargesJson', chargesJson);

      const sampleData = {
        "testy": "testy",
        "charges": [
          {
            "amount": "1800.00",
            "description": "Construction Management/Administration Phase",
          }
        ]
      }

      renderer.addCustomDataSource({
        format: render.DataSource.JSON,
        alias: "jsonStr",
        data: JSON.stringify(sampleData)
        // data: chargesResults[0]
      });

      renderer.addCustomDataSource({
        format: render.DataSource.OBJECT,
        alias: "jsonObj",
        data: sampleData
        // data: chargesResults[0]
      });

      const objectData = gatherMetrics();

      renderer.addCustomDataSource({
        alias: 'metricsObj',
        format: render.DataSource.OBJECT,
        data: objectData
      })
      renderer.addCustomDataSource({
        alias: 'metricsJson',
        format: render.DataSource.JSON,
        data: JSON.stringify(objectData)
      })

      //set a key of timeRun to the a formatted short version of the current date and time
      // data['timeRun'] = new Date().toLocaleString('en-US', { timeZone: 'America/New_York' });

      record.submitFields({
        type: record.Type.INVOICE,
        id: id,
        values: {
          custbody_invoice_charges_cache: chargesJson
          // custbody_invoice_charges_cache: chargesResults
        }
      });

      renderer.addSearchResults({
        templateName: 'SEARCH', searchResult: chargesResults 
      });

      renderer.addRecord({
        templateName: 'record',
        record: record.load({
          type: record.Type.INVOICE,
          id: id
        })
      });

      // Render the renderer as a pdf
      // pdfFileToCreate = renderer.renderAsPdf();

      pdfFileToCreate = render.transaction({ entityId: Number(id) });

      const invoiceName = search.lookupFields({
        type: search.Type.INVOICE,
        id: id,
        columns: ['tranid']
      }).tranid;

      log.debug('invoiceName', invoiceName);

      // Define the file name
      pdfFileToCreate.name = 'Invoice ' + invoiceName + '.pdf';

      fileType = pdfFileToCreate.fileType;

      log.debug('Render the transaction next');

      // pdfFileToCreate = render.transaction({ entityId: Number(id) });

      return pdfFileToCreate;
    }

    // function formatResultsAsArray(results) {
    //   let arr = [];

    //   for (let i = 0; i < results.length; i++) {
    //     let obj = {};

    //     for (let j = 0; j < results[i].columns.length; j++) {
    //       obj[results[i].columns[j].name] = results[i].getValue(results[i].columns[j]);
    //     }
    //     log.debug('obj', obj);
    //     arr.push(obj);
    //   }

    //   return arr;
    // }

    const gatherMetrics = () => ({
      start: new Date(),
      runtime: 7.8903,
      governanceUsed: 883,
      author: ['Eric', 'T', 'Grubaugh']
    })

    function searchCharges(id) {
      const chargeSearch = search.create({
        type: "charge",
        filters:
          [
            ["invoice.internalid", "anyof", id]
          ],
        columns:
          [
            search.createColumn({ name: "use", label: "Charge Use" }),
            search.createColumn({ name: "chargetype", label: "Charge Type" }),
            search.createColumn({ name: "id", label: "Charge ID" }),
            search.createColumn({ name: "billto", label: "Customer:Project" }),
            search.createColumn({ name: "rate", label: "Rate" }),
            search.createColumn({ name: "quantity", label: "Quantity" }),
            search.createColumn({ name: "discountamount", label: "Discount Amount" }),
            search.createColumn({ name: "amount", label: "Amount" }),
            search.createColumn({ name: "chargedate", label: "Date" }),
            search.createColumn({ name: "billdate", label: "Bill Date" }),
            search.createColumn({ name: "servicestartdate", label: "Service Start Date" }),
            search.createColumn({ name: "serviceenddate", label: "Service End Date" }),
            search.createColumn({ name: "stage", label: "Charge Stage" }),
            search.createColumn({ name: "runid", label: "Charge Run ID" }),
            search.createColumn({ name: "description", label: "Description" }),
            search.createColumn({ name: "memo", label: "Memo" })
          ]
      });

      return chargeSearch.run().getRange({ start: 0, end: 1000 });
    }

    return {
      onRequest: onRequest
    };
  }
);
c
@ZachO First, you're getting back the data for the identified record with
searchCharges()
?
@ZachO How are you attempting to access the custom data in your template?
z
Correct yeah, since addSearchResults did not work, I was invoking searchCharges (intended to get charges related to a given invoice), and then setting a rich text field to the result. This was allowing me to source the rich text field in the template, but it is apparently a string. The version above uses the searchResult.toJSON method to convert from a result set member
Here is the relevant part of the template. Included all the current things Im trying to source the fields, the custbody on the record being the only one that 'works'
Copy code
<#if SEARCH?has_content>
   <p style="color: green"> Saved Search results: </p> 
   <table style="width: 100%; margin-top: 10px;">
      <thead>
         <tr><th>trandate</th><th>amount</th><th>entity</th></tr>
      </thead>
      <#list SEARCH as item> <tr> <td>${item.description}</td> <td>${item.amount}</td> <td>${item.description}</td> </tr> </#list>
   </table>
<#else>
   <p style="color: red"> No Saved Search results </p>
</#if>

<#if metricsJson?has_content>
   <p style="color: green"> metricsJson variable: ${metricsJson} </p>
<#else>
   <p style="color: red"> No jsonStr variable </p>
</#if>

<#if metricsObj?has_content>
   <p style="color: green"> metricsObj variable: ${metricsObj} </p>
<#else>
   <p style="color: red"> No metricsObj variable </p>
</#if>

<#if record.custbody_invoice_charges_cache?has_content>
   <p style="color: green"> record.custbody_invoice_charges_cache variable: ${record.custbody_invoice_charges_cache} </p>
  <p>${record.custbody_invoice_charges_cache.recordType}</p>
<#else>
   <p style="color: red"> No record.custbody_invoice_charges_cache variable </p>
</#if>
Results:
Here is the script logs and what the body field looks like in the UI, and the body field definition (trying to give a complete picture)
e
Is this when using
renderer.renderAsPdf()
or
render.transaction()
?
z
Currently using render.transaction. Sorry, I think I mentioned renderAsPdf before which was it the template I inherited, but I noticed trying to remove the render.transaction call borked the output. I commented out the renderAsPdf currently
e
So you won't see any of your custom datasources using
transaction()
because that doesn't use the
renderer
you've created
🔔 1
z
That makes a lot of sense, the renderer variable was overwritten in the code. Thank you! P.S. big fan of your work
❤️ 1