Control when widgets are updated

By default, a QuickApp automatically updates any and all widgets that reference a dynamic variable whose value has changed. However, this might not be the desired behavior. QuickApps provide a number of mechanisms to give the user more control.

In Display data in a graphics widget, the grid and graphics widgets were both updated automatically whenever an item was selected in either of the drop-down widgets. The queries in these examples are fairly simple, and the data sets on which they operate are relatively small. However, the cost to run each query for every widget, in terms of both time and system resources, could become quite high if the queries were more complex or the data sets were substantially larger. In this particular example, you might want to allow the user to update the grid and graphics widgets manually once they are finished making all of their selections.

A widget is invalidated when the data associated with it changes. This can happen when there is a change to one or more of the dynamic variables that a widget references. For instance, when the aggregate_by dynamic variable changes, both the grid and graphics widgets are invalidated. The default behavior of a QuickApp is to automatically update any invalidated widgets. However, you can manually control when widgets are updated by setting the mode_ attribute to manual in the opening <dynamic> tag or in the <widget> tag for a particular widget. If you set this attribute in the opening <dynamic> tag, all widgets need to be manually updated. If you set this attribute in a <widget> tag, only that particular widget needs to be manually updated. A widget in this state will remain invalidated until it is told to update.

You can control the behavior of an invalidated widget by setting the invmode_ attribute for that widget. You can set the invmode_ to hide, so that the widget is hidden until it is updated; to block, which prevents any interaction with the widget; or to none, in which case the widget remains in its previous state until it is updated.

You can provide the user with a button to manually update invalidated widgets. When the button is clicked, the invalidated widgets will receive a message to update, and the widgets will run their associated queries and refresh the data that they are displaying.

To control when widgets are updated:

  1. Set the mode_ attribute in the opening <dynamic> tag to manual.
    
      ...
    
    <dynamic selection="19" product_master="pub.doc.retail.product" 
     sales_detail="pub.doc.retail.salesdetail" aggregate_by="groupdesc_prod"
     mode_="manual">
    
      ...
    
    

    When the mode_ attribute for the <dynamic> is set to manual, any invalidated widgets remain in that state until manually told to update. By default, the widgets are blocked and prevented from any interaction until they receive a message to update. A default message of Please wait... is presented to the user.

  2. In the <widget> tag for both the grid and graphics widgets, set the invmsg_ attribute to a more descriptive message.
    
      ...
    
      <widget class_="grid" base_="{@sales_detail}" insert_="sales_by_date"
       prod_table="{@product_master}" department="{@selection}" 
       group_by="{@aggregate_by}"
       invmsg_="Click Run for changes to take effect"/>
      <widget class_="graphics" base_="{@sales_detail}" width_="800"
       insert_="sales_by_date" prod_table="{@product_master}" 
       department="{@selection}" group_by="{@aggregate_by}"
       invmsg_="Click Run for changes to take effect">
        <graphspec>
          <chart type="bar">
            <data x="{@aggregate_by}" y="tot_sales"/>
            <ticks xrot="45"/>
            <style xaxissize="10" yaxissize="10"/>
          </chart>
        </graphspec>
      </widget>
    </dynamic>
    
  3. Click Apply.

    The QuickApp will not show any visible changes until the widgets are invalidated.

  4. Select Brand from the Aggregate by drop-down menu.

    Selecting an item from the drop-down menu invalidates both the grid and graphics widgets, because they both depend on the dynamic variable changed by the drop-down widget. They are both blocked and display the message specified by the invmsg_ attribute.

When widgets are in an invalidated state, they must be told when to update. This can be done by using a button widget with type_="submit". When the button is clicked, it will trigger a refresh of all invalidated widgets.

  1. Add a button widget to update invalidated widgets.
    Note: It makes sense to put the new button in the layout containing the drop-down widgets since it is one of the controls for the state of the QuickApp.
    
      ...
    
    <dynamic selection="19" product_master="pub.doc.retail.product" 
     sales_detail="pub.doc.retail.salesdetail" aggregate_by="groupdesc_prod"
     mode_="manual">
      <layout background_="lightblue" border_="10">
        <widget class_="dropdown" base_="{@product_master}" 
         inputwidth_="250" value_="@selection"
         label_="Department:" labelwidth_="75">
          <tabu label="Tabulation on Product Master" breaks="deptdesc">
            <break col="deptdesc" sort="up"/>
            <tcol source="dept" name="dept" fun="first" 
             label="First`Department"/>
          </tabu>
          <colord cols="dept,deptdesc"/>
        </widget>
        <widget class_="dropdown" value_="@aggregate_by" 
         label_="Aggregate by:" labelwidth_="75" inputwidth_="250">
          <table>groupdesc_prod,Group;brand_prod,Brand
          </table>
        </widget>
        <widget class_="button" text_="Run" type_="submit"/>
        <ignore>
          <widget class_="text" text_="Current selection: {@selection}"/>
        </ignore>
      </layout>
      <widget class_="grid" base_="{@sales_detail}" insert_="sales_by_date"
       prod_table="{@product_master}" department="{@selection}" 
       group_by="{@aggregate_by}"
       invmsg_="Click Run for changes to take effect"/>
    
      ...
    
    

    A <widget> with class_="button" has been added. Because the type_ attribute is set to submit, a message will be sent to all invalidated widgets when this button is clicked.

  2. Click Apply.

    The Run button is added to the layout under the drop-down widgets.

  3. Select DAIRY DELI from the Department drop-down menu.

    The grid and graphics widgets are invalidated. They are both blocked and display the specified message.

  4. Select Brand from the Aggregate by drop-down menu.

    The grid and graphics widgets remain invalidated and blocked.

  5. Click Run.

    An update message is sent to the invalidated widgets. The widgets run their respective queries with the new values of the dynamic variables in the QuickApp and refresh the data they are displaying.

For certain widgets, you may want their effects to take place immediately. For instance, you may want to have a checkbox in this QuickApp that controls whether or not to display the chart; however, you would not want to force the user to click the Run button upon every interaction with the checkbox.

  1. Add a checkbox to control whether or not the chart is visible.
    
      ...
    
    <dynamic selection="19" product_master="pub.doc.retail.product" 
     sales_detail="pub.doc.retail.salesdetail" aggregate_by="groupdesc_prod"
     mode_="manual" display_chart="1">
      <layout background_="lightblue" border_="10">
        <widget class_="dropdown" base_="{@product_master}" 
         inputwidth_="250" value_="@selection"
         label_="Department:" labelwidth_="75">
          <tabu label="Tabulation on Product Master" breaks="deptdesc">
            <break col="deptdesc" sort="up"/>
            <tcol source="dept" name="dept" fun="first" 
             label="First`Department"/>
          </tabu>
          <colord cols="dept,deptdesc"/>
        </widget>
        <widget class_="dropdown" value_="@aggregate_by" 
         label_="Aggregate by:" labelwidth_="75" inputwidth_="250">
          <table>groupdesc_prod,Group;brand_prod,Brand
          </table>
        </widget>
        <widget class_="checkbox" label_="Display Chart" 
         value_="@display_chart"/>
        <widget class_="button" text_="Run" type_="submit"/>
        <ignore>
          <widget class_="text" text_="Current selection: {@selection}"/>
        </ignore>
      </layout>
      <widget class_="grid" base_="{@sales_detail}" insert_="sales_by_date"
       prod_table="{@product_master}" department="{@selection}" 
       group_by="{@aggregate_by}"
       invmsg_="Click Run for changes to take effect"/>
      <widget class_="graphics" base_="{@sales_detail}" width_="800"
       insert_="sales_by_date" prod_table="{@product_master}" 
       department="{@selection}" group_by="{@aggregate_by}"
       invmsg_="Click Run for changes to take effect"
       visible_="{@display_chart}">
        <graphspec>
          <chart type="bar">
            <data x="{@aggregate_by}" y="tot_sales"/>
            <ticks xrot="45"/>
            <style xaxissize="10" yaxissize="10"/>
          </chart>
        </graphspec>
      </widget>
    </dynamic>
    

    A new dynamic variable display_chart has been added to the <dynamic> with an initial value of 1.

    A checkbox widget has been added to the <layout>, which will set the value of display_chart depending on whether or not the checkbox is selected. (The default behavior for a checkbox widget is to set the value of the dynamic variable specified by the value_ attribute to 1 when selected and 0 when the checkbox is cleared.)

    A visible_ attribute, which is set to the value of display_chart, has been added to the graphics widget. Therefore, when display_chart is set to 1, the graphics widget is visible, and when display_chart is set to 0, the graphics widget is hidden.

  2. Click Apply.

    The Display Chart checkbox is added to the layout containing the drop-down widgets.

  3. Clear the Display Chart checkbox.

    Because the graphics widget is dependent on the display_chart dynamic variable, and because mode_ is set to manual in the opening <dynamic> tag, the graphics widget is invalidated and therefore blocked.

    To refresh the widget, you must click the Run button. However, in this instance, the change to the dynamic variable associated with the checkbox does not have any effect on the query associated with the graphics widget. It only dictates whether or not to display the graphics widget. It would make more sense for the graphics widget to update automatically.

One of the ways to control when a widget is invalidated is to set the mode_ in the opening <dynamic> to auto (or just omit it for the default behavior) and to use the holdfor_ attribute in each <widget> tag. For a particular widget, the holdfor_ attribute allows you to specify a list of dynamic variables that, when changed, will invalidate that widget.

For this example, it would make sense to list the aggregate_by and selection dynamic variables in the holdfor_ attribute for both the grid and graphics widgets (since their queries depend on those two dynamic variables), but not to include the display_chart dynamic variable. That way, when the user selects an item from either the Aggregate by or Department drop-down widgets, the grid and graphics widgets will become invalidated, and the user will need to click Run to refresh them. However, when the checkbox is selected or cleared, the graphics widget will be displayed or hidden automatically.

  1. Change the update_ attribute to auto in the opening <dynamic> tag, and set the holdfor_ attribute for the grid and graphics widget to the aggregate_by and selection dynamic variables.
    
      ...
    
    <dynamic selection="19" product_master="pub.doc.retail.product" 
     sales_detail="pub.doc.retail.salesdetail" aggregate_by="groupdesc_prod"
     mode_="auto" display_chart="1">
    
      ...
    
      <widget class_="grid" base_="{@sales_detail}" insert_="sales_by_date"
       prod_table="{@product_master}" department="{@selection}" 
       group_by="{@aggregate_by}"
       invmsg_="Click Run for changes to take effect"
       holdfor_="@aggregate_by,@selection"/>
      <widget class_="graphics" base_="{@sales_detail}" width_="800"
       insert_="sales_by_date" prod_table="{@product_master}" 
       department="{@selection}" group_by="{@aggregate_by}"
       invmsg_="Click Run for changes to take effect"
       visible_="{@display_chart}" holdfor_="@aggregate_by,@selection">
        <graphspec>
          <chart type="bar">
            <data x="{@aggregate_by}" y="tot_sales"/>
            <ticks xrot="45"/>
            <style xaxissize="10" yaxissize="10"/>
          </chart>
        </graphspec>
      </widget>
    </dynamic>
    
    Note: Instead of setting the mode_ attribute to auto in the opening <dynamic> tag, you could omit the mode_ attribute entirely since the default behavior for the QuickApp is to automatically update all invalidated widgets.
  2. Click Apply.
  3. Clear the Display Chart checkbox.

    The chart is hidden.

  4. Select the Display Chart checkbox.

    The chart is displayed.

  5. Select Brand from the Aggregate by drop-down menu.

    The grid and graphics widget are both invalidated and blocked.

  6. Select DAIRY DELI from the Department drop-down menu.

    The grid and graphics widget remain invalidated and blocked.

  7. Click Run.

    The grid and graphics widgets are updated to reflect the new values for the aggregate_by and selection dynamic variables.

Cumulative QuickApp code

The Macro Language code for the QuickApp up to this point is:

<defblock name="sales_by_date" prod_table="" department="" group_by="">
  <link table2="{@prod_table}" col="sku" col2="sku" 
   suffix="_prod" type="select">
    <sel value="dept={@department}"/>
  </link>
  <tabu label="Tabulation on Sales Detail" breaks="{@group_by}">
    <tcol source="xsales" fun="sum" name="tot_sales" 
     label="Sum of`Extended Sales" format="type:currency"/>
  </tabu>
  <sort col="tot_sales" dir="down"/>
  <sel value="({@group_by} <> '')"/>
</defblock>
<dynamic selection="19" product_master="pub.doc.retail.product" 
 sales_detail="pub.doc.retail.salesdetail" aggregate_by="groupdesc_prod" 
 mode_="auto" display_chart="1">
  <layout background_="lightblue" border_="10">
    <widget class_="dropdown" base_="{@product_master}" 
     inputwidth_="250" value_="@selection" 
     label_="Department:" labelwidth_="75">
      <tabu label="Tabulation on Product Master" breaks="deptdesc">
        <break col="deptdesc" sort="up"/>
        <tcol source="dept" name="dept" fun="first" label="First`Department"/>
      </tabu>
      <colord cols="dept,deptdesc"/>
    </widget>
    <widget class_="dropdown" value_="@aggregate_by" 
     label_="Aggregate by:" labelwidth_="75" inputwidth_="250">
      <table>groupdesc_prod,Group;brand_prod,Brand
      </table>
    </widget>
    <widget class_="checkbox" label_="Display Chart" 
     value_="@display_chart"/>
    <widget class_="button" text_="Run" type_="submit"/>
    <ignore>
      <widget class_="text" text_="Current selection: {@selection}"/>
    </ignore>
  </layout>
  <widget class_="grid" base_="{@sales_detail}" 
   insert_="sales_by_date" prod_table="{@product_master}" 
   department="{@selection}" group_by="{@aggregate_by}" 
   invmsg_="Click Run for changes to take effect" 
   holdfor_="@aggregate_by,@selection"/>
  <widget class_="graphics" base_="{@sales_detail}" width_="800" 
   insert_="sales_by_date" prod_table="{@product_master}" 
   department="{@selection}" group_by="{@aggregate_by}" 
   invmsg_="Click Run for changes to take effect" 
   visible_="{@display_chart}" holdfor_="@aggregate_by,@selection">
    <graphspec>
      <chart type="bar">
        <data x="{@aggregate_by}" y="tot_sales"/>
        <ticks xrot="45"/>
        <style xaxissize="10" yaxissize="10"/>
      </chart>
    </graphspec>
  </widget>
</dynamic>