Using Object Data Tables

An ObjectDataTable is a table that – instead of simply containing text information – embeds objects such as JavaScript functions that can be accessed via callback functions.

The following sample relies on the DemoDataModel example, which can be found in the HOOPS Publish package in the publishadvanced sample solution. For simplicity, all of the code below should be added to DemoDataModel.cpp in the DemoDataModel project.

Defining an ObjectDataTable

First, let’s write a simple function that will create our ObjectDataTable into which we’ll later insert our JavaScript actions. We’ll define our A3DPDFDataTableData just as we would any other table in HOOPS Publish, but to create the table we’ll call A3DPDFObjectDataTableCreate(), which will create an ObjectDataTable:

A3DStatus stCreateObjectTable(A3DPDFDocument* pDoc, const std::vector<std::vector<std::string> > &aRows, A3DPDFDataTable*& pDataTable)
{
    A3DStatus iRet = A3D_SUCCESS;
    A3DUns32 iNbRows = (int)aRows.size();
    A3DUns32 iNbCols = (iNbRows > 0) ? (int)aRows[0].size() : 0;
    A3DPDFDataTableData sTableData;
    A3D_INITIALIZE_DATA(A3DPDFDataTableData, sTableData);
    sTableData.m_iNbRows = iNbRows;
    sTableData.m_iNbCols = iNbCols;
    sTableData.m_ppcTexts = (A3DUTF8Char**)malloc(iNbRows * iNbCols * A3DUns32(sizeof(A3DUTF8Char*)));
    for (A3DUns32 ir = 0; ir < iNbRows; ir++)
    {
        for (A3DUns32 ic = 0; ic < iNbCols; ic++)
            mallocAndSetString(
            (A3DUTF8Char*)(char*)aRows[ir][ic].c_str(),       // src
                sTableData.m_ppcTexts[ir*iNbCols + ic]);        // dest
    }
    CHECK_RET(A3DPDFObjectDataTableCreate(pDoc, &sTableData, &pDataTable));
    stFreeTable(sTableData);
    return iRet;
}

Injecting JavaScript

Now that we’ve created an ObjectDataTable, we can add custom actions by embedding JavaScript code. Continuing with the DemoDataModel example from the Publish package, we’ll replace the existing stBuildDocument() function with some new code specifically for our ObjectDataTable. The full code for our stBuildDocument() function is available at the bottom of this page.

Let’s define a simple JavaScript function to serve as a callback in our PDF document. This callback function can be triggered in JavaScript actions we’ll later assign to individual fields:

std::basic_ostringstream<char> osJsOnDoc;
osJsOnDoc << "\n\
\n\
function scrolltable_callback_withuserdata(i, j, userdata)\n\
{\n\
  app.alert( { cMsg:\" JavaScript action on cell [\"+i+\" - \"+j+\"]\\n model name \"+userdata.modelname+\" ; owner \"+userdata.modelowner+\"  \" } );\n\
  console.println(\"in scrolltable_callback ; i=\"+i+\" - j=\"+j);\n\
  console.println(\"in scrolltable_callback userdata=\"+userdata);\n\
  console.println(\"in scrolltable_callback userdata.modelname=\"+userdata.modelname);\n\
  \n\
}\n\
";
CHECK_RET(A3DPDFDocumentAddJavaScriptFromString(pDoc, "STCallbacks", osJsOnDoc.str().c_str()));

In this snippet, we’re simply reading our JavaScript function into a C++ stream and calling A3DPDFDocumentAddJavaScriptFromString() to embed it in our PDF document.

Next we’ll create a A3DPDFDataTable and add rows to it one at a time. Each row is a standard vector object, and each element in the vector represents a column in the A3DPDFDataTable. Within each column, we’ll define an actionjscallback function and an actionjscallbackdata object, which we’ll use to write some information to an alert window whenever a user clicks on that particular cell.

In addition, we’ll define an actionurl, which will launch a browser window and retrieve a URL whenever the user clicks on the associated icon.

A3DPDFDataTable* pDataTable_Bom = NULL;
// structure for Tables definitions
std::vector<std::vector<std::string> > aRows;
std::vector<std::string> aOneRow;
aRows.clear();
aOneRow.clear();
aOneRow.push_back("{ caption:\"1\" }"); // default action: action datamodel
aOneRow.push_back("{ caption:\"crank damper\", actionjscallback:\"scrolltable_callback_withuserdata\", actionjscallbackdata: { modelname:\"crank_damper\" , modelowner:\"Joe\" } }");
aOneRow.push_back("{ caption:\"2\"}"); // default action: action datamodel
aOneRow.push_back("{ caption:\"iron\"}"); // default action: action datamodel
sIconId = stStoreIcon(pDoc, std::string(ICONS_PATH) + "crank_damper.jpg");
aOneRow.push_back(std::string("{ icon:\"") + sIconId + std::string("\", actionurl:\"http://localhost:11180/hoops_web_viewer.html?scs=crank_damper.scs\" }"));
aRows.push_back(aOneRow);
//...add 5 more rows here.
stCreateObjectTable(pDoc, aRows, pDataTable_Bom);
// --- Widgets binding for Table BOM
const A3DInt32 piMapColumns_Bom[] = { 0, 1, 2, 3, 4, 5 };
CHECK_RET(A3DPDFScrollTableBindToTable(pScrollTableBom, pDataTable_Bom, sizeof(piMapColumns_Bom) / sizeof(A3DInt32), piMapColumns_Bom));

In the final section of the snippet, we’ve called A3DPDFScrollTableBindToTable() to embed our ObjectDataTable inside of a A3DPDFScrollTable.

Running the Sample

Compiling and then running the DemoDataModel sample will create a new PDF in the samples directory called sample_DemoDataModel.pdf:

../../_images/object_table.png

The PDF will contain a part list – clicking on a part name will trigger the actionjscallback custom event, popping up an alert window containing the data retrieved with our JavaScript functions. Clicking on an image icon will launch the actionurl in a browser.

../../_images/object_table_alert.png

Full Sample Code

A3DStatus stCreateObjectTable(A3DPDFDocument* pDoc, const std::vector<std::vector<std::string> > &aRows, A3DPDFDataTable*& pDataTable)
{
    A3DStatus iRet = A3D_SUCCESS;
    A3DUns32 iNbRows = (int)aRows.size();
    A3DUns32 iNbCols = (iNbRows > 0) ? (int)aRows[0].size() : 0;
    A3DPDFDataTableData sTableData;
    A3D_INITIALIZE_DATA(A3DPDFDataTableData, sTableData);
    sTableData.m_iNbRows = iNbRows;
    sTableData.m_iNbCols = iNbCols;
    sTableData.m_ppcTexts = (A3DUTF8Char**)malloc(iNbRows * iNbCols * A3DUns32(sizeof(A3DUTF8Char*)));
    for (A3DUns32 ir = 0; ir < iNbRows; ir++)
    {
        for (A3DUns32 ic = 0; ic < iNbCols; ic++)
            mallocAndSetString(
            (A3DUTF8Char*)(char*)aRows[ir][ic].c_str(),       // src
                sTableData.m_ppcTexts[ir*iNbCols + ic]);        // dest
    }
    CHECK_RET(A3DPDFObjectDataTableCreate(pDoc, &sTableData, &pDataTable));
    stFreeTable(sTableData);
    return iRet;
}
// In DemoDataModel example, replace existing stBuildDocument() function with this code.
A3DStatus stBuildDocument(A3DPDFDocument* pDoc)
{
    A3DStatus iRet = A3D_SUCCESS;
    A3DPDFPage* pPage = NULL;
    int indexpage = 0;
    std::string sSuffixFields;
    // populating the document metadata
    A3DPDFDocumentInformationData sInformationData;
    A3D_INITIALIZE_DATA(A3DPDFDocumentInformationData, sInformationData);
    sInformationData.m_pcTitle = (char*) "HOOPS Publish Functionalities";
    sInformationData.m_pcCreator = (char*) "HOOPS Publish Sample Application";
    sInformationData.m_pcSubject = (char*) "This sample demoes the data model functionality of HOOPS Publish";
    sInformationData.m_pcAuthor = (char*) "Tech Soft 3D";
    CHECK_RET(A3DPDFDocumentSetInformation(pDoc, &sInformationData));
    // creating a document page from scratch
    A3DPDFPageData2 sPageData;
    A3D_INITIALIZE_DATA(A3DPDFPageData2, sPageData);
    sPageData.m_ePageOrientation = kA3DPDFPageLandscape;
    sPageData.m_ePageSize = kA3DPDFPageLetter;     // standard letter format: 612 x 792
    const int iPageWidth = 792, iPageHeight = 612;
    CHECK_RET(A3DPDFDocumentAppendNewPage2(pDoc, &sPageData, &pPage));
    if (pPage != NULL)
    {
        std::string sIconId;
        // create Scroll table on the page
        A3DPDFScrollTable* pScrollTableBom = NULL;
        std::vector<int> aiWidthCols; // in points
        aiWidthCols.push_back(30);
        aiWidthCols.push_back(170);
        aiWidthCols.push_back(30);
        aiWidthCols.push_back(60);
        aiWidthCols.push_back(50);
        std::vector<std::string> aRowHeader;
        aRowHeader.push_back("No");
        aRowHeader.push_back("NAME PART");
        aRowHeader.push_back("QTY");
        aRowHeader.push_back("MATERIAL");
        aRowHeader.push_back("SNAPSHOT");
        std::vector<A3DPDFEColumnType> aeTypes;
        aeTypes.push_back(kA3DPDFTextContent);
        aeTypes.push_back(kA3DPDFTextContent);
        aeTypes.push_back(kA3DPDFTextContent);
        aeTypes.push_back(kA3DPDFTextContent);
        aeTypes.push_back(kA3DPDFImageContent);
        CHECK_RET(stAddScrollTable(pDoc, pPage,
            20, 500, // iPosLeft, iPosTop
            15, //iSliderWidth
            3,  // iNbRowsInFrame
            20, // iHeightRowHeader - in points
            40, // iHeightRows - in points
            aiWidthCols, // in points
            aRowHeader,
            aeTypes,
            &pScrollTableBom));
        // store JavaScript functions on the document. these functions can be used by JavaScript stored on the fields.
        std::basic_ostringstream<char> osJsOnDoc;
        osJsOnDoc << "\n\
\n\
function scrolltable_callback_withuserdata(i, j, userdata)\n\
{\n\
  app.alert( { cMsg:\" JavaScript action on cell [\"+i+\" - \"+j+\"]\\n model name \"+userdata.modelname+\" ; owner \"+userdata.modelowner+\"  \" } );\n\
  console.println(\"in scrolltable_callback ; i=\"+i+\" - j=\"+j);\n\
  console.println(\"in scrolltable_callback userdata=\"+userdata);\n\
  console.println(\"in scrolltable_callback userdata.modelname=\"+userdata.modelname);\n\
  \n\
}\n\
";
        CHECK_RET(A3DPDFDocumentAddJavaScriptFromString(pDoc, "STCallbacks", osJsOnDoc.str().c_str()));
        A3DPDFDataTable* pDataTable_Bom = NULL;
        //CHECK_RET(stCreateTable_Sple1_Manufacturers(pDoc, pDataTable_Bom));
        // structure for Tables definitions
        std::vector<std::vector<std::string> > aRows;
        std::vector<std::string> aOneRow;
        aRows.clear();
        aOneRow.clear();
        aOneRow.push_back("{ caption:\"1\" }"); // default action: action datamodel
        aOneRow.push_back("{ caption:\"crank damper\", actionjscallback:\"scrolltable_callback_withuserdata\", actionjscallbackdata: { modelname:\"crank_damper\" , modelowner:\"Joe\" } }");
        aOneRow.push_back("{ caption:\"2\"}"); // default action: action datamodel
        aOneRow.push_back("{ caption:\"iron\"}"); // default action: action datamodel
        sIconId = stStoreIcon(pDoc, std::string(ICONS_PATH) + "crank_damper.jpg");
        aOneRow.push_back(std::string("{ icon:\"") + sIconId + std::string("\", actionurl:\"http://localhost:11180/hoops_web_viewer.html?scs=crank_damper.scs\" }"));
        aRows.push_back(aOneRow);
        aOneRow.clear();
        aOneRow.push_back("{ caption:\"2\" }");
        aOneRow.push_back("{ caption:\"crank crankshaft\", actionjscallback:\"scrolltable_callback_withuserdata\", actionjscallbackdata: { modelname:\"crank_crankshaft\" , modelowner:\"James\" } }");
        aOneRow.push_back("{ caption:\"1\"}");// default: action datamodel
        aOneRow.push_back("{ caption:\"steel\" }"); // default: action datamodel
        sIconId = stStoreIcon(pDoc, std::string(ICONS_PATH) + "crank_crankshaft.jpg");
        aOneRow.push_back(std::string("{ icon:\"") + sIconId + std::string("\", actionurl:\"http://localhost:11180/hoops_web_viewer.html?scs=crank_crankshaft.scs\" }"));
        aRows.push_back(aOneRow);
        aOneRow.clear();
        aOneRow.push_back("{ caption:\"3\" }");
        aOneRow.push_back("{ caption:\"crank valve\", actionjscallback:\"scrolltable_callback_withuserdata\", actionjscallbackdata: { modelname:\"crank_valve\" , modelowner:\"Bill\" } }");
        aOneRow.push_back("{ caption:\"1\"}");
        aOneRow.push_back("{ caption:\"steel\", action:\"_rowdatamodel_\"}");
        sIconId = stStoreIcon(pDoc, std::string(ICONS_PATH) + "crank_valve.jpg");
        aOneRow.push_back(std::string("{ icon:\"") + sIconId + std::string("\", actionurl:\"http://localhost:11180/hoops_web_viewer.html?scs=crank_valve.scs\" }"));
        aRows.push_back(aOneRow);
        aOneRow.clear();
        aOneRow.push_back("{ caption:\"4\" }");
        aOneRow.push_back("{ caption:\"crank connectingrod boltnut\", actionjscallback:\"scrolltable_callback_withuserdata\", actionjscallbackdata: { modelname:\"crank_connectingrod_boltnut\" , modelowner:\"Neal\" } }");
        aOneRow.push_back("{ caption:\"1\"}");// default: action datamodel
        aOneRow.push_back("{ caption:\"steel\" }"); // default: action datamodel
        sIconId = stStoreIcon(pDoc, std::string(ICONS_PATH) + "crank_connectingrod_boltnut.jpg");
        aOneRow.push_back(std::string("{ icon:\"") + sIconId + std::string("\", actionurl:\"http://localhost:11180/hoops_web_viewer.html?scs=crank_connectingrod_boltnut.scs\" }"));
        aRows.push_back(aOneRow);
        aOneRow.clear();
        aOneRow.push_back("{ caption:\"5\" }");
        aOneRow.push_back("{ caption:\"crank pistonpin\", actionjscallback:\"scrolltable_callback_withuserdata\", actionjscallbackdata: { modelname:\"crank_pistonpin\" , modelowner:\"Henry\" } }");
        aOneRow.push_back("{ caption:\"2\"}");// default: action datamodel
        aOneRow.push_back("{ caption:\"steel2\" }"); // default: action datamodel
        sIconId = stStoreIcon(pDoc, std::string(ICONS_PATH) + "crank_pistonpin.jpg");
        aOneRow.push_back(std::string("{ icon:\"") + sIconId + std::string("\", actionurl:\"http://localhost:11180/hoops_web_viewer.html?scs=crank_pistonpin.scs\" }"));
        aRows.push_back(aOneRow);
        aOneRow.clear();
        aOneRow.push_back("{ caption:\"6\" }");
        aOneRow.push_back("{ caption:\"crank connectingrod bushing\", actionjscallback:\"scrolltable_callback_withuserdata\", actionjscallbackdata: { modelname:\"crank_connectingrod_bushing\" , modelowner:\"Jack\" } }");
        aOneRow.push_back("{ caption:\"3\"}");// default: action datamodel
        aOneRow.push_back("{ caption:\"steel3\" }"); // default: action datamodel
        sIconId = stStoreIcon(pDoc, std::string(ICONS_PATH) + "crank_connectingrod_bushing.jpg");
        aOneRow.push_back(std::string("{ icon:\"") + sIconId + std::string("\", actionurl:\"http://localhost:11180/hoops_web_viewer.html?scs=crank_connectingrod_bushing.scs\" }"));
        aRows.push_back(aOneRow);
        stCreateObjectTable(pDoc, aRows, pDataTable_Bom);
        // --- Widgets binding for Table BOM
        const A3DInt32 piMapColumns_Bom[] = { 0, 1, 2, 3, 4, 5 };
        CHECK_RET(A3DPDFScrollTableBindToTable(pScrollTableBom, pDataTable_Bom, sizeof(piMapColumns_Bom) / sizeof(A3DInt32), piMapColumns_Bom));
        indexpage++;
    }
    // end document
    CHECK_RET(A3DPDFDocumentSaveEx(pDoc, kA3DPDFSaveFull|kA3DPDFSaveOptimized, OUT_FILE));
    return iRet;
}