dynamically create component upon click of button

Conlan O'RourkeConlan O'Rourke ✭✭✭✭
edited June 29, 2020 in Questions
I've been working off this tutorial here: https://docs.skuid.com/latest/en/skuid/javascript/dynamic-model-component-creation.html#dynamic-creation-of-models

However, the table that gets built in this tutorial is built upon page load...using a Custom Inline Component JS resource.

For my purpose, I would like to build a table dynamically upon click of a button.

My use case is --> user loads page that displays a table from a declaratively built model, then selects rows in that table, clicks a button, which runs an inline snippet to dynamically build another model based on the rows selected by the user and dynamically builds another table.

I don't seem to be able to wrap the sample code in a callable function when it is located within an Inline Component JS Resource...

So I moved the code to an Inline Resource where I am able to wrap it in a function and then call that function from an Inline Snippet (run on button click).

But that's where I get a bit lost...

...since I'm not using an Inline Component JS Resource, I think I need to call skuid.componentType.register to register my component, and then reference that component when I call skuid.Component.factory....

$.when(skuid.model.load(allModels)).then(function(){
console.log('Model Loaded...Building Table...');
$xml = skuid.utils.makeXMLDoc; var xmlDefinition = $xml( '<skootable showconditions="true" showsavecancel="false" searchmethod="server" searchbox="true" showexportbuttons="true" pagesize="50" createrecords="false" model="Assessment_Response__c" mode="read"/>' ).append( $xml('<fields/>').append( $xml('<field id="Name" allowordering="true"/>'), $xml('<field id="QuestionText001__c" overridemetadata="true" ogdisplaytype="STRING" displaytype="STRING" defaultvaluetype="fieldvalue"/>'), $xml('<field id="QuestionText002__c" overridemetadata="true" ogdisplaytype="STRING" displaytype="STRING" defaultvaluetype="fieldvalue"/>'), $xml('<field id="QuestionText003__c" overridemetadata="true" ogdisplaytype="STRING" displaytype="STRING" defaultvaluetype="fieldvalue"/>') ), $xml('<rowactions/>'), $xml('<massactions/>'), $xml('<views><view type="standard"/></views>') ); //Register my dynamic Table Component var myDynamicTable = skuid.componentType.register('DynamicTable',function(element,xmlDefinition,component){}); element.html('Loading Table...'); element.empty(); skuid.component.factory({ element: myDynamicTable, xmlDefinition: xmlDefinition }); console.log("Table Built"); }); };
My table isn't displaying on page preview however...any guidance from the community would be much appreciated!

Comments

  • edited September 5, 2019
    Conlan, the easiest way to do this would be to switch to defining an "Inline (Component)" JavaScript Resource named "DynamicTable", which can be included in your (V1) page using the "Custom" Component. Within the render logic of that DynamicTable custom component, you can setup a subscription to an event, and you can then have your button Publish that event to initiate the main logic of dynamically creating a Model and dynamically creating a Table component. Here is an example of how to do this:

    var element = arguments[0],
        component = arguments[2],
        $xml = skuid.utils.makeXMLDoc;

    // Setup an event subscription that can be published from actions, e.g. when a button is clicked
    component.subscribeToPageEvent("loadDynamicTable", function() {
        // Remove any preexisting child components
        component.unregisterChildComponents();
        // Load dynamically-created models
        skuid.model.load(allModels).then(function(){
            console.log("Models Loaded...Building Table...");
        // Dynamically create a new table component
        var xmlDef = $xml(
           '<skootable showconditions="true" showsavecancel="false" searchmethod="server" searchbox="true" showexportbuttons="true" pagesize="50" createrecords="false" model="People" mode="read"/>'
            ).append(
                $xml('<fields/>').append(
                    $xml('<field id="FirstName" allowordering="true"/>'),
                    $xml('<field id="LastName" allowordering="true"/>')
                ),
                $xml('<rowactions/>'),
                $xml('<massactions/>'),
                $xml('<views><view type="standard"/></views>')
            );
        var newTable = component.createChildComponent(xmlDef, element);
        console.log("new table created", newTable);
    });


    Here is a full page example using Ui-Only Models that demonstrates how to do this:

    <skuidpage unsavedchangeswarning="yes" personalizationmode="client">
    <models>
    <model id="People" query="true" createrowifnonefound="false" datasource="Ui-Only" processonclient="true">
    <fields>
    <field id="FirstName" displaytype="TEXT" length="255" label="First name"/>
    <field id="LastName" displaytype="TEXT" length="255" label="Last Name"/>
    </fields>
    <conditions/>
    <actions/>
    </model>
    </models>
    <components>
    <pagetitle model="People" uniqueid="sk-3ueW-848">
    <maintitle>Create dynamic table</maintitle>
    <subtitle>
    <template>{{Model.label}}</template>
    </subtitle>
    <actions>
    <action type="multi" label="Create dynamic table" uniqueid="sk-3ueX-861">
    <actions>
    <action type="publish" snippet="LoadDynamicTable" scope="page" event="loadDynamicTable">
    <params/>
    </action>
    </actions>
    </action>
    </actions>
    </pagetitle>
    <custom name="DynamicTable" uniqueid="TheDynamicTable"/>
    </components>
    <resources>
    <labels/>
    <javascript>
    <jsitem location="inlinecomponent" name="DynamicTable" cachelocation="false" url="">var element = arguments[0],
        component = arguments[2],
        $xml = skuid.utils.makeXMLDoc;

    // Setup an event subscription that 
    component.subscribeToPageEvent("loadDynamicTable", function() {
        // Remove any preexisting child components
        component.unregisterChildComponents();
        
        // Dynamically create a new table component
        var xmlDef = $xml(
           '&lt;skootable showconditions="true" showsavecancel="false" searchmethod="server" searchbox="true" showexportbuttons="true" pagesize="50" createrecords="false" model="People" mode="read"/&gt;'
            ).append(
                $xml('&lt;fields/&gt;').append(
                    $xml('&lt;field id="FirstName" allowordering="true"/&gt;'),
                    $xml('&lt;field id="LastName" allowordering="true"/&gt;')
                ),
                $xml('&lt;rowactions/&gt;'),
                $xml('&lt;massactions/&gt;'),
                $xml('&lt;views&gt;&lt;view type="standard"/&gt;&lt;/views&gt;')
            );
        var newTable = component.createChildComponent(xmlDef, element);
        // console.log("new table created", newTable);
    });</jsitem>
    </javascript>
    <css/>
    <actionsequences uniqueid="sk-3ue2-560">
    <actionsequence id="e2973f9e-47b9-4da5-92b0-f93f32186cd9" label="create a default row" type="event-triggered" event-scope="component" event-name="page.rendered">
    <description/>
    <actions>
    <action type="createRow" model="People" appendorprepend="prepend" defaultmodefornewitems="edit" affectedrows="context">
    <defaults>
    <default valuesource="fieldvalue" field="FirstName" enclosevalueinquotes="true" value="Joe"/>
    <default valuesource="fieldvalue" field="LastName" enclosevalueinquotes="true" value="Schmoe"/>
    </defaults>
    </action>
    </actions>
    </actionsequence>
    </actionsequences>
    </resources>
    <styles>
    <styleitem type="background" bgtype="none"/>
    </styles>
    </skuidpage>


    image

    image
  • Conlan O'RourkeConlan O'Rourke ✭✭✭✭
    edited January 15, 2020
    Zach, I really appreciate the help!! I don't think I would have gotten this without your input, especially since it looks like there are some functions you are using that are not yet listed on the API docs.

    I expanded on your sample page to build a Prototype Report Builder (using only UI-Only models - as I said, it's a prototype). XML Below. 

    I already have applications where I know this will very useful for users to have, and as I continue to build this out to have more robust functionality, I may post to Skuid Labs on GitHub.

    Thanks again for your help!

    imagetinue




    <skuidpage unsavedchangeswarning="yes" personalizationmode="client" showheader="false" showsidebar="false">     <models>         <model id="ReportableColumns" limit="20" query="true" createrowifnonefound="false" datasource="Ui-Only" processonclient="true">             <fields>                 <field id="ColumnName" displaytype="TEXT" label="Column"/>             </fields>             <conditions/>             <actions/>         </model>         <model id="ReportingColumns" limit="20" query="true" createrowifnonefound="false" datasource="Ui-Only" processonclient="true">             <fields>                 <field id="ColumnName" displaytype="TEXT" label="Column"/>             </fields>             <conditions/>             <actions/>         </model>         <model id="ReportablePeople" query="true" createrowifnonefound="false" datasource="Ui-Only" processonclient="true">             <fields>                 <field id="FirstName" displaytype="TEXT" length="255" label="First name"/>                 <field id="LastName" displaytype="TEXT" length="255" label="Last Name"/>                 <field id="MiddleName" displaytype="TEXT" label="Middle Name"/>             </fields>             <conditions/>             <actions/>         </model>     </models>     <components>         <grid uniqueid="sk-3_yD-1392">             <divisions>                 <division behavior="fit" verticalalign="top">                     <components/>                 </division>             </divisions>             <styles>                 <styleitem type="background" bgtype="none"/>             </styles>         </grid>         <buttonset model="QuestionFields" uniqueid="sk-Y48-3817">             <buttons>                 <button type="multi" label="Run Report" uniqueid="sk-Y49-3824" icon="sk-icon-magic">                     <actions>                         <action type="publish" behavior="hide" componentid="ReportBuilder" fieldmodel="ViewModel" affectedrows="context" field="View" enclosevalueinquotes="true" value="Report" scope="page" event="loadDynamicTable">                             <params/>                         </action>                     </actions>                     <renderconditions logictype="and"/>                     <enableconditions logictype="and" message="Please Select Columns for Reporting">                         <condition type="fieldvalue" enclosevalueinquotes="true" fieldmodel="ReportingColumns" sourcetype="modelproperty" sourceproperty="hasRows"/>                     </enableconditions>                 </button>             </buttons>         </buttonset>         <grid uniqueid="ReportBuilder" columngutter="5em">             <divisions>                 <division behavior="flex" verticalalign="top" minwidth="100px" ratio="1">                     <components>                         <skootable showconditions="true" showsavecancel="false" showerrorsinline="true" searchmethod="client" searchbox="false" showexportbuttons="false" hideheader="false" hidefooter="true" pagesize="all" alwaysresetpagination="false" createrecords="false" model="ReportablePeople" mode="readonly" allowcolumnreordering="false" responsive="true" uniqueid="sk-3_xf-1127" heading="People for Reporting">                             <fields>                                 <field id="FirstName" hideable="false" uniqueid="fi-3_xn-1195" readonly="true" columnwidth="10px" showbydefault="true"/>                                 <field id="MiddleName" hideable="false" uniqueid="fi-3_xn-1196" readonly="true" columnwidth="10px" showbydefault="true"/>                                 <field id="LastName" hideable="false" uniqueid="fi-3_xn-1197" readonly="true" columnwidth="10px" showbydefault="true"/>                             </fields>                             <rowactions/>                             <massactions usefirstitemasdefault="true"/>                             <views>                                 <view type="standard"/>                             </views>                             <searchfields/>                             <actions defaultlabel="Global Actions" defaulticon="sk-icon-magic" usefirstitemasdefault="true"/>                         </skootable>                     </components>                 </division>                 <division behavior="specified" verticalalign="top" width="400px">                     <components>                         <wrapper uniqueid="sk-Y2k-3395">                             <components>                                 <deck searchmethod="server" searchbox="false" columngutter=".75em" rowgutter=".75em" model="ReportableColumns" filtersposition="top" filterswidth="150px" showsavecancel="false" behavior="flex" verticalalign="top" ratio="1" minwidth="250px" hideheader="false" hidefooter="true" uniqueid="sk-X-9-1545" pagesize="all" heading="Reportable Columns" setmaxwidth="manual" maxwidth="250px" emptysearchbehavior="query" alwaysresetpagination="true">                                     <components>                                         <wrapper uniqueid="sk-X-N-1656" cssclass="ReportingCards">                                             <components>                                                 <template multiple="false" uniqueid="sk-X-J-1646" model="ReportableColumns" allowhtml="true">                                                     <contents>&lt;span style="color:white"&gt;{{ColumnName}}&lt;/span&gt;</contents>                                                     <conditions/>                                                 </template>                                             </components>                                             <styles>                                                 <styleitem type="background"/>                                                 <styleitem type="border"/>                                                 <styleitem type="size"/>                                             </styles>                                             <renderconditions logictype="and"/>                                             <interactions>                                                 <interaction type="tap">                                                     <action type="blockUI" message="Adding..." timeout="1500"/>                                                     <action type="adoptRows" sourcemodel="ReportableColumns" targetmodel="ReportingColumns" affectedrows="context"/>                                                     <action type="abandonRows" model="ReportableColumns" affectedrows="context"/>                                                 </interaction>                                             </interactions>                                         </wrapper>                                     </components>                                     <massactions/>                                     <interactions/>                                     <actions/>                                     <styles>                                         <styleitem type="border" borders="all">                                             <styles>                                                 <styleitem property="border" value="5px solid #1f497d"/>                                                 <styleitem property="box-sizing" value="border-box"/>                                             </styles>                                         </styleitem>                                         <styleitem property="background" value="#1f497d"/>                                         <styleitem property="border-radius" value="10em"/>                                         <styleitem property="box-shadow" value="0 8px 16px"/>                                         <styleitem property="cursor" value="pointer"/>                                     </styles>                                     <searchfields/>                                 </deck>                             </components>                             <styles>                                 <styleitem type="background"/>                                 <styleitem type="border" margin="none" padding="left,">                                     <styles>                                         <styleitem property="padding-left" value="25px"/>                                         <styleitem property="box-sizing" value="border-box"/>                                     </styles>                                 </styleitem>                                 <styleitem type="size" height="collapse" width="custom">                                     <styles>                                         <styleitem property="min-width" value="500px"/>                                     </styles>                                 </styleitem>                             </styles>                         </wrapper>                     </components>                 </division>                 <division behavior="flex" verticalalign="top" minwidth="100px" ratio="1">                     <components>                         <deck searchmethod="server" searchbox="false" columngutter=".75em" rowgutter=".75em" model="ReportingColumns" filtersposition="top" filterswidth="150px" showsavecancel="false" behavior="flex" verticalalign="top" ratio="1" minwidth="250px" hideheader="false" hidefooter="true" uniqueid="sk-X-D-1604" pagesize="all" heading="Reporting Columns" setmaxwidth="manual" maxwidth="250px" emptysearchbehavior="query">                             <components>                                 <wrapper uniqueid="sk-Y07-1907" cssclass="ReportingCards">                                     <components>                                         <template multiple="false" uniqueid="sk-X-R-1692" model="ReportingColumns" allowhtml="true">                                             <contents>&lt;span style="color:white"&gt;{{ColumnName}}&lt;/span&gt;</contents>                                             <conditions/>                                         </template>                                     </components>                                     <styles>                                         <styleitem type="background"/>                                         <styleitem type="border"/>                                         <styleitem type="size"/>                                     </styles>                                     <interactions>                                         <interaction type="tap">                                             <action type="blockUI" message="Removing..." timeout="1500"/>                                             <action type="adoptRows" sourcemodel="ReportingColumns" targetmodel="ReportableColumns" affectedrows="context"/>                                             <action type="abandonRows" model="ReportingColumns" affectedrows="context"/>                                         </interaction>                                     </interactions>                                 </wrapper>                             </components>                             <massactions/>                             <interactions/>                             <actions/>                             <styles>                                 <styleitem type="border" borders="all">                                     <styles>                                         <styleitem property="border" value="5px solid #8064a2"/>                                         <styleitem property="box-sizing" value="border-box"/>                                     </styles>                                 </styleitem>                                 <styleitem property="background" value="#8064a2"/>                                 <styleitem property="border-radius" value="10em"/>                                 <styleitem property="box-shadow" value="0 8px 16px"/>                                 <styleitem property="cursor" value="pointer"/>                             </styles>                             <searchfields/>                         </deck>                     </components>                 </division>             </divisions>             <styles>                 <styleitem type="background" bgtype="none"/>             </styles>             <renderconditions logictype="and"/>         </grid>         <richtext multiple="false" uniqueid="sk-1cu-276">             <contents>&lt;p&gt;&amp;nbsp;&lt;/p&gt;  &lt;p&gt;&amp;nbsp;&lt;/p&gt;  &lt;hr /&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; </contents>         </richtext>         <custom name="DynamicTable" uniqueid="TheDynamicTable">             <renderconditions logictype="and"/>         </custom>     </components>     <resources>         <labels/>         <javascript>             <jsitem location="inlinecomponent" name="DynamicTable" cachelocation="false" url="">var element = arguments[0],     component = arguments[2],     $xml = skuid.utils.makeXMLDoc;     $ = skuid.$;     reportingColumns = skuid.$M('ReportingColumns');     reportableColumns = skuid.$M('ReportableColumns');     reportablePeople = skuid.$M('ReportablePeople');   // Setup an event subscription that  component.subscribeToPageEvent("loadDynamicTable", function() {     // Remove any preexisting child components     component.unregisterChildComponents();          // Build Assessment Model var allModels = []; var ReportingPeople = new skuid.model.Model(); //ReportingPeople.objectName = 'Assessment_Response__c'; ReportingPeople.id = 'ReportingPeople'; ReportingPeople.recordsLimit = 5; ReportingPeople.fields = [   { id: 'FirstName', label: 'First Name' },   { id: 'MiddleName', label: 'Middle Name' },   { id: 'LastName', label: 'Last Name' }, ]; ReportingPeople.conditions = [];  allModels.push(ReportingPeople); $.each(allModels,function(){   this.initialize().register(); });  console.log("Reporting People Fields:"); console.log(ReportingPeople.fields);  console.log("Building Array of ReportableFields"); var reportableFields = [];  $.each(reportingColumns.data, function(){         var thisColumn = reportableFields.unshift($xml('&lt;field id="'+this.ColumnName+'" allowordering="true"/&gt;'));     } );  console.log("Reportable Fields:"); console.log(reportableFields);  ReportingPeople.adoptRows(reportablePeople.getRows(), {doAppend: true});       console.log("Building Report Table...");         // Dynamically create a new table component     var xmlDef = $xml(        '&lt;skootable heading="People Report" showconditions="true" showsavecancel="false" searchmethod="server" searchbox="false" showexportbuttons="true" pagesize="all" createrecords="false" model="ReportingPeople" mode="read"/&gt;'         ).append(             $xml('&lt;fields/&gt;').append(reportableFields),             $xml('&lt;rowactions/&gt;'),             $xml('&lt;massactions/&gt;'),             $xml('&lt;views&gt;&lt;view type="standard"/&gt;&lt;/views&gt;')         );     var newTable = component.createChildComponent(xmlDef, element);     // console.log("new table created", newTable); }); console.log("Table Built"); </jsitem>         </javascript>         <css>             <cssitem location="inline" name="Cards" cachelocation="false">/* .ReportingCards:hover{     opacity: 0.5; } */  .sk-deck article:hover{     opacity: 0.5; }</cssitem>         </css>         <actionsequences uniqueid="sk-3ue2-560">             <actionsequence id="e2973f9e-47b9-4da5-92b0-f93f32186cd9" label="create a default row" type="event-triggered" event-scope="component" event-name="page.rendered" uniqueid="sk-3_Wx-376">                 <description/>                 <actions>                     <action type="createRow" model="ReportableColumns" appendorprepend="prepend" defaultmodefornewitems="edit" affectedrows="context">                         <defaults>                             <default type="fieldvalue" field="ColumnName" enclosevalueinquotes="true" value="LastName"/>                         </defaults>                     </action>                     <action type="createRow" model="ReportableColumns" appendorprepend="prepend" defaultmodefornewitems="edit" affectedrows="context">                         <defaults>                             <default type="fieldvalue" field="ColumnName" enclosevalueinquotes="true" value="MiddleName"/>                         </defaults>                     </action>                     <action type="createRow" model="ReportableColumns" appendorprepend="prepend" defaultmodefornewitems="edit" affectedrows="context">                         <defaults>                             <default type="fieldvalue" field="ColumnName" enclosevalueinquotes="true" value="FirstName"/>                         </defaults>                     </action>                     <action type="save">                         <models>                             <model>ReportableColumns</model>                         </models>                     </action>                     <action type="createRow" model="ReportablePeople" appendorprepend="prepend" defaultmodefornewitems="edit" affectedrows="context">                         <defaults>                             <default type="fieldvalue" field="FirstName" enclosevalueinquotes="true" value="Jane"/>                             <default type="fieldvalue" field="MiddleName" enclosevalueinquotes="true" value="Harriett"/>                             <default type="fieldvalue" field="LastName" enclosevalueinquotes="true" value="Doe"/>                         </defaults>                     </action>                     <action type="createRow" model="ReportablePeople" appendorprepend="prepend" defaultmodefornewitems="edit" affectedrows="context">                         <defaults>                             <default type="fieldvalue" field="FirstName" enclosevalueinquotes="true" value="John"/>                             <default type="fieldvalue" field="MiddleName" enclosevalueinquotes="true" value="Henry"/>                             <default type="fieldvalue" field="LastName" enclosevalueinquotes="true" value="Doe"/>                         </defaults>                     </action>                     <action type="save">                         <models>                             <model>ReportablePeople</model>                         </models>                     </action>                 </actions>             </actionsequence>         </actionsequences>     </resources>     <styles>         <styleitem type="background" bgtype="none"/>     </styles> </skuidpage>
  • Mark LMark L ✭✭✭✭
    edited June 29, 2020
    This is great. It seems relatively straightforward for a single component / dynamic table. Can this strategy be adopted and utilized for dynamic Decks & Deck sub-components with dynamic context? How might one go about that? Specifically a dynamic component as well as dynamic sub-components underneath it (perhaps even dynamic sub-sub components)
  • Mark LMark L ✭✭✭✭
    edited June 29, 2020
    I'm working off of this example and I've gotten everything working except I can only seem to get the "loadDynamicTable" event to trigger the component rendering if it's invoked by a button on the page.

    If for instance I have it set up on a Model Action to publish "loadDynamicTable", or am trying to use javascript skuid.events.publish('loadDynamicTable'); the component.subscribeToPageEvent doesn't seem to fire.

    Any ideas?

    EDIT: I figured it out. The custom component was hidden on the page, it would appear you can only run published events for it while it is visible on the page.
  • Conlan O'RourkeConlan O'Rourke ✭✭✭✭
    edited June 29, 2020
    Mark - are you by chance getting this to work on Decks & Deck Sub-Components, as you had previously asked? If so, I (and likely others) would be very interested if you would be willing to share how you were able to modify this solution for those Components :)


  • Mark LMark L ✭✭✭✭
    edited January 25, 2020
    I haven't gone down the route of experimenting with dynamic decks with subcomponents just yet. I'm using a table currently, and have gotten that to work similarly to the design outlined in the thread.

    My thought is that if I start going the route of decks would be to add the Deck as a subcomponent to the Custom Component, and also add any subcomponents to that Deck as needed. I'd first set it all up statically, and then copy that XML making my dynamic changes as needed to create the XML string in javascript to create the dynamic deck and subcomponents.

    I still need to experiment with all this. Not sure if it will actually work. When I do I'll make sure to post my results.
  • Mark LMark L ✭✭✭✭
    edited February 3, 2020
    Update:

    I was able to get dynamic Decks with sub-components working. I first created a normal static Deck as well as Templates underneath the deck that had some "dummy" context rules and then copied the XML and with a custom component similar to this method for tables constructed a dynamic string for the component. I didn't need to create sub components specifically, I just included them in the XML string for the custom component along with the deck the same way it was outlined in the XML on the example static components on the page.
  • Any chance this same functionality might be possible in V2? I don't see a custom component in the V2 builder, and all of the docs for custom components are sending me to a 403 forbidden error page... 😕

Sign In or Register to comment.

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!