Skip to Content

Lumira 2.1 contains a very powerful feature. While in former Design Studio and Lumira version an app had constant number of components, in 2.1 you can also create and delete components via scripting. This give you – the app developer – much more freedom to react on external factors, e.g. specific data from your data sources or preferences of the user. We have developed it in 2.0 for Discovery: The Discovery tools is mainly a Designer app and required such a feature, e.g. to create data sources, charts and crosstabs on user interaction or to load and unload stories. In version 2.1 you are free to use it, but be warned: while it is a very powerful feature it is also dangerous: if you don’t exactly know what, you are doing, you could make your app slow or create an unsupported state.

Most of the dynamic features are exposed with the new technical component called “COMPONENTS”. Its methods are also called the Components-API.

Creating a Component

Try it out: simply create an App or a Composite and add a “COMPONENTS” technical component. Then start scripting, e.g. adding a button’s ON_CLICK event.

var myNewText = COMPONENTS.createComponent(ComponentType.Text);
myNewText.setText("Hello world");
myNewText.setLeftMargin(100);
myNewText.setTopMargin(100);

What is special with this script expect that you have one component more in your app after it is executed? Maybe the fact that type of the component returned by COMPONENTS.createComponent depends on the argument.

The parameter “type” is an entry from a special enumeration “ComponentType” – which automatically contains all available component types, including SDK components and Composites. The return type is – according to the documentation “GenericComponentBase” – which is only a placeholder for the concrete type specified by the “type” argument. If you hover, you will see that the function really returned a “Text” component – so you can afterward use all script APIs that are available on a Text component.

If you want to create the new component inside of some container component, e.g. in a Panel, pass the container as second argument.

Deleting a Component

If you later want to delete the component again, you should store it in a Global Script variable: Create a global script variable of type “Text” and modify your creation script like this:

gMyNewText = COMPONENTS.createComponent(ComponentType.Text);
gMyNewText.setText("Hello world");
gMyNewText.setLeftMargin(100);
gMyNewText.setTopMargin(100);

Later in another script you can simply delete the new component:

COMPONENTS.deleteComponent(gMyNewText);

Creating and Deleting Multiple Components

In most cases, you will create multiple components in a loop, e.g. using some data as input:

var dims = DS_1.getDimensions();
dims.forEach(function(dim, index) {
  var text = COMPONENTS.createComponent(ComponentType.Text);
  text.setLeftMargin(20);
  text.setTopMargin(20 + index*30);
  text.setWidth(200);
  text.setText(dim.name + " (" + dim.text + ")"); 
});

This is fine as long as you don’t need to delete the created components later.

If you need to delete the components, e.g. to create new ones with nested data, there are two typical strategies:

  • Keep all created components in a global array
  • Create a container, keep it and later delete the container.

Strategy 1

Create a global variable array – best using the base type “Component”, as it allows you to mix components of multiple types:

// Delete all old dynamic components
gaCreatedComponents.forEach(function(oldComp) {
       COMPONENTS.deleteComponent(oldComp);
});

// Trick: make gaCreatedComponents empty
gaCreatedComponents = [me]; // add a dummy component
gaCreatedComponents.pop(); // remove the dummy

// Now create new components
var dims = DS_1.getDimensions();
dims.forEach(function(dim, index) {
  var text = COMPONENTS.createComponent(ComponentType.Text);
  text.setLeftMargin(20);
  text.setTopMargin(20 + index*30);
  text.setWidth(200);
  text.setText(dim.name + " (" + dim.text + ")"); 
  gaCreatedComponents.push(text);
});

Strategy 2

Create a global script variable with a container component, e.g. a Panel.

Now the first thing you do it to create the panel. All other components will go into it. Later you can completely delete the Panel to implicitly delete all other components as well:

if (gPanel != undefined) {
       // This will delete the old panel and all old componnents contained in it
       COMPONENTS.deleteComponent(gPanel);   
}

// Create a new Panel
gPanel = COMPONENTS.createComponent(ComponentType.Panel);

// Now create new components
var dims = DS_1.getDimensions();
dims.forEach(function(dim, index) {
  var text = COMPONENTS.createComponent(ComponentType.Text, gPanel);
  text.setLeftMargin(20);
  text.setTopMargin(20 + index*30);
  text.setWidth(200);
  text.setText(dim.name + " (" + dim.text + ")"); 
});

gPanel.setWidth(200);
gPanel.setHeight(dims.length * 30 + 20);

Creating and Deleting Composites

You can also create instances of Composites. This is the way how Discovery loads and unloads Stories. If you create a composite called “COMP_1” in you document (LUMX file), your will find an entry like “LUM_CC762C8C979A2EEEAFE788A0760955D9_COMP1” in your ComponentType enumeration. The long “number” is internal ID for the document in which the composite is contained. For local documents, it is derived from the LUMX file name. For BIP documents it uses the document’s CUID. Composites from the same document as your current APP can be always created. If you want to create instances of a Composite from a different document, you must have at least one Composite from that document statically contained in your app – else we would not know that there is a reference to the document.

What’s next?

In my next blog I will show you another interesting new API for dynamic app: DS.getDataSelections() – which allows you to iterate over result sets. The third blog explains the APIs COMPONENTS.createBinding and COMPONENTS.getBinding, which I skipped here.

I’m looking forward for you feedback, questions, proposals etc.

To report this post you need to login first.

21 Comments

You must be Logged on to comment or reply to a post.

  1. Mustafa Bensan

    Hi Reiner,

    This is indeed a great new feature which I think will open up interesting new possibilities.  I have the following questions:

    1.  When specifying the container parameter of the createCompinent() API, can we reference a specific cell in a Grid Layout container or a specific block in the Adaptive Layout container?
    2. Can we in future expect drag and drop functionality to be supported with the dynamic component creation feature?  

    Thanks,

    Mustafa.

     

    (0) 
    1. Reiner Hille-Doering Post author

      Hello Mustafa,

      great that you like it.

      1. Yes. You find a even a sample in our new Document templates where a composite and a block are created dynamically and the block is added to an adaptive layout container. Thanks to my colleague Christina for the excellent samples.
      2. I assume you want to have our “design mode” exposed that is used inside of Designer and Discovery? Indeed Discovery uses another technical component “AUTHORING” that allows setting containers to design mode. And another components that is currently only used in Discovery acts as palette to drag components from. Those components are currently not mature enough to be exposed to Designer users. But maybe we will have the chance to expose them in a future release.

      Reiner.

      (0) 
      1. Mustafa Bensan

        Hi Reiner,

        Thanks for the feedback.  Regarding point 2, you have understood my intent exactly.  I’ll look forward to the possibility of “design mode” in future then.

        One more question: given that Technical components can be included in Bookmarks, if we include a COMPONENTS technical component, can we assume that dynamically instantiated components will be preserved when bookmarks are saved and subsequently re-loaded?

        Mustafa.

        (0) 
        1. Reiner Hille-Doering Post author

          Hello Mustafa,

          the COMPONENTS technical components itself has no state. Saving it into a bookmark would have no effect.

          Persisting dynamically created components into a bookmark is tricky:  The basic idea with the new 2.x “delta bookmarks is to store only the modified properties into the bookmark and use the rest from the app. As the dynamically created component does not exist in the app, we can’t calculate a delta – and end up with a bookmark containing the whole component definition. And this is what we wanted to avoid – because it makes the stored bookmarks fragile.

          I think a much better strategy is to keep some additional information in global script variables. Those variables would go into the bookmark. The script should then consider the script variable when it re-creates the dynamic components.

          (0) 
          1. Mustafa Bensan

            Hi Reiner,

            I suspected persisting dynamic components via bookmarked global variables would be the recommended approach, thanks.  Since you have mentioned that Discovery is essentially a standalone Designer app, I would be curious to know the technique used when saving stories to persist the story metadata as a Lumira document?

            Mustafa.

             

            (0) 
            1. Reiner Hille-Doering Post author

              Hello Mustafa, thanks for the question.

              Discovery does not store Bookmarks, but real Composites: It copies a template composite to a new one and loads an instance of the newly created composite. The the composite is modified, either via Design Mode or via COMPONENTS API etc.

              Finally the current state is saved directly into the Composite’s content.bicomp. The APIs for self-modifying apps or Composite-creators are not yet public.

              Regards,

              Reiner.

              (0) 
              1. Mustafa Bensan

                Thanks for the clarification, Reiner.  That makes perfect sense now.  Are there any plans to make the APIs for self-modifying apps or Composite-creators public in a future release of Lumira?

                Regards,

                Mustafa.

                (0) 
  2. Mustafa Bensan

    Hi Reiner,

    One suggestion for consideration.  As you know, since dynamically generated components have no properties, they must be set via scripting.  Therefore, for the createComponent() API to be practical and truly effective, very granular scripting should now be provided for setting component properties.  So far, only a very basic subset of the standard component properties have been scriptable via setters.  To fully support dynamic component creation and configuration, ideally ALL available properties should be scriptable.  Some examples are:

    1.  All configuration properties of a Chart
    2.  All properties of a Dimension Filter or Filter Panel, including selection of dimensions

     

     

    Thanks,

    Mustafa.

    (0) 
    1. Reiner Hille-Doering Post author

      Hello Mustafa,

      thanks to remind me that I have not explained the COMPONENT.copyProperties(). This will copy all properties from one component to another one of the same type. You can use e.g. to have an invisible “template chart” where you have configured all common properties. After you have created your new chart, copy the template and set the ones that are specific.

      Regards,

      Reiner.

      (0) 
      1. Mustafa Bensan

        Hi Reiner,

        Thanks for pointing out the copyProperties() API.  However, I think that creating invisible template components is not a very clean approach and could be impractical in certain scenarios such as the following:

        1. In cases where the properties of a dynamically generated component (such as a chart), are defined by user interaction/selection, it would not make sense to create a template component for each possible property combination that the user could select;
        2. In cases where a user-selected data source is dynamically generated and assigned to a component such as a Dimension Filter or Filter Panel, it would not be possible to create template components in advance at design-time because some properties would be dependent on the dimensions available in the data source which are not known at design time.

        So extending the scope of property scriptability would be very beneficial for allowing the development of truly dynamic apps.

        Regards,

        Mustafa.

         

        (0) 
        1. Reiner Hille-Doering Post author

          Hello Mustafa,

          you are right. We also have a generic property modification API in the Component-API. It is used in such cases by Discovery as you describe.

          But the syntax is not really nice, therefore we decided to keep it out of the product.

          Best regards,

          Reiner.

          (0) 
  3. Mike Howles

    Reiner,

     

    Really excellent feature we now have here…  Question, I’ve tried to see if I can set event handlers for dynamically generated components, such as the Button component having ‘On Click’ logic.  It doesn’t appear possible unless I’m missing something.

    Is this planned functionality?  Workaround thoughts include creating a “wrapper” composite containing just a Button with some generic script maybe….  I tried using the copyProperties technique but it doesn’t appear to pass over Event Handler script.

    (0) 
    1. Reiner Hille-Doering Post author

      Hello Mike,

      hm, copyProperties should also copy scripts – but I didn’t test it so far, maybe there is a reason for not copying scripts.

      Else indeed the current API wouldn’t allow to set scripts dynamically. The discovery app uses the mentioned private API that allow generic property changes to all properties. But this would end up having script that create scripts dynamically – which would be like the evil eval in JavaScript. Such a feature is really a little over complicated for a tool like Designer 🙂 .

       

      Reiner.

      (0) 
  4. Beat Stoller

    Works well and I think it would be most useful together with the item ADAPTIVE_LAYOUT. When inserting generated components into an adaptive layout, the blocks are generated automatically. But I see no option to set the parameters of a block. 3 default columns is just not always right. There are no scriptable parameters for the block. Has anyone tried this?

    (0) 
  5. Andre Strässer

    When I saw this functionality and thought a bit about the possibilities, I fell backwards off the chair. This feature is so so great, thank you very much for this.  😀

    (0) 
  6. Thilo Knötzele

    Hi Rainer,

    awesome Feature!

    Just wondered about which technical Name the generated components will get? Is it the same as in Lumira Designer PANEL_1, PANEL_2 etc? Or some generated GUIDs?

    Regards

    Thilo

     

     

     

     

    (0) 

Leave a Reply