Dropdown value list based on another field

Is there any way to change the list of choices in a drop down field based on the value of another field or maybe user’s attribute like community?

Hi Sherwin

You’d have to create your own custom control. PSO did have one for 5.7 called the cascade dropdown but I’m not sure for 6.x

Cheers
James

In 6.5.2 you can set up multiple dropdowns and hide all but one based on the user’s community (for example we do this using a conditional rule in Field Visibility that evaluates to true if PSXUserContext/User/SessionObject/sys_community = 1010) or their role (for example we do this using a conditional rule that evaluates to true if PSXUserContext/Roles/RoleName LIKE Admin%)

Having multiple fields like this would be a PITA. Not for your users, because they could all be labelled the same, but for implementing stuff like autoslots, which might have to test several fields rather than one, if this dropdown is intended to contain information you might filter on. It also means more columns in your backend database. Adding such duplicated fields to searches in the Content Explorer or Active Assembly would be troublesome, if not impossible.

[QUOTE=jimbo;7124]
You’d have to create your own custom control. PSO did have one for 5.7 called the cascade dropdown but I’m not sure for 6.x[/QUOTE]

More recently we’ve done Ajax based controls for cascading dropdowns. This works better than the old “load up all possible choices when you open the editor” approach that the first versions used.

The mechanics are not different between 5.7 and 6.x

Dave

snippet: control definition
add to: Rhythmyx/rx_resources/stylesheets/rx_Templates.xsl


  <!-- Start ajax_CascadingDropDown -->

  <psxctl:ControlMeta name="ajax_CascadingDropDown" dimension="single">
    <psxctl:Description>a Cascading drop down combo box for populating another Child dropdown that uses AJAX calls to limit amount of data loaded on the page.</psxctl:Description>
    <psxctl:ParamList>
      <psxctl:Param name="cascadeId" datatype="String" paramtype="generic">
        <psxctl:Description>This should be the same for each set of connected Drop Down controls. This should not contain spaces or any other non-alphanumeric characters.</psxctl:Description>
      </psxctl:Param>
      <psxctl:Param name="parent" datatype="String" paramtype="generic">
        <psxctl:Description>The name of the direct parent in the cascade. Leave blank if this control will be the top level of the cascade.</psxctl:Description>
      </psxctl:Param>
      <psxctl:Param name="queryField" datatype="String" paramtype="generic">
        <psxctl:Description>The http query field in which values from this field will be submitted. If nothing is provided, the field name will be used.</psxctl:Description>
      </psxctl:Param>
      <psxctl:Param name="dataUrl" datatype="String" paramtype="generic">
        <psxctl:Description>The URL to load data for this field. Selected values for ancestors in the cascade will be sent as query parameters keyed by their column name.</psxctl:Description>
      </psxctl:Param>
      <psxctl:Param name="cacheRoot" datatype="String" paramtype="generic">
        <psxctl:Description>When provided, options will be loaded from files published to at_cache in the specified directory.</psxctl:Description>
      </psxctl:Param>
      <psxctl:Param name="id" datatype="String" paramtype="generic">
        <psxctl:Description>XHTML 1.0 attribute</psxctl:Description>
      </psxctl:Param>
      <psxctl:Param name="class" datatype="String" paramtype="generic">
        <psxctl:Description>XHTML 1.0 attribute</psxctl:Description>
      </psxctl:Param>
      <psxctl:Param name="style" datatype="String" paramtype="generic">
        <psxctl:Description>XHTML 1.0 attribute</psxctl:Description>
      </psxctl:Param>
      <psxctl:Param name="size" datatype="Number" paramtype="generic">
        <psxctl:Description>XHTML 1.0 attribute</psxctl:Description>
      </psxctl:Param>
      <psxctl:Param name="tabindex" datatype="Number" paramtype="generic">
        <psxctl:Description>XHTML 1.0 attribute</psxctl:Description>
      </psxctl:Param>
      <psxctl:Param name="disabled" datatype="String" paramtype="generic">
        <psxctl:Description>XHTML 1.0 attribute</psxctl:Description>
      </psxctl:Param>
      <psxctl:Param name="formname" datatype="String" paramtype="jscript">
        <psxctl:Description>Name of the form that contains this control. Setting this to anything other than 'EditForm' will cause errors and your drop down controls will not work.</psxctl:Description>
        <psxctl:DefaultValue>EditForm</psxctl:DefaultValue>
      </psxctl:Param>
    </psxctl:ParamList>
    <psxctl:AssociatedFileList>
      <psxctl:FileDescriptor name="jquery.js" type="script" mimetype="text/javascript">
        <psxctl:FileLocation>../rx_resources/js/jquery.js</psxctl:FileLocation>
        <psxctl:Timestamp/>
      </psxctl:FileDescriptor>
      <psxctl:FileDescriptor name="ajax_cascading.js" type="script" mimetype="text/javascript">
        <psxctl:FileLocation>../rx_resources/js/ajax_cascading.js</psxctl:FileLocation>
        <psxctl:Timestamp/>
      </psxctl:FileDescriptor>
    </psxctl:AssociatedFileList>
  </psxctl:ControlMeta>

  <!-- template for building cascase arrays -->
  <xsl:template match="Control[@name='ajax_CascadingDropDown' ]" mode="buildAjaxDropDownArrays">
    <xsl:variable name="myName" select="@paramName"/> cascadeFields_<xsl:value-of
      select="ParamList/Param[@name='cascadeId']"/>[cascadeFields_<xsl:value-of
      select="ParamList/Param[@name='cascadeId']"/>.length] = "<xsl:value-of
      select="@paramName"/>"; cascadeValues_<xsl:value-of
      select="ParamList/Param[@name='cascadeId']"/>[cascadeValues_<xsl:value-of
      select="ParamList/Param[@name='cascadeId']"/>.length] = "<xsl:call-template
      name="escape-javascript">
      <xsl:with-param name="string" select="Value"/>
    </xsl:call-template>"; cascadeUrlMap[ "<xsl:value-of select="@paramName"/>" ] =
      "<xsl:call-template name="escape-javascript">
      <xsl:with-param name="string" select="ParamList/Param[@name='dataUrl']"/>
    </xsl:call-template>"; cascadeCacheRootMap[ "<xsl:value-of select="@paramName"/>" ] =
      "<xsl:call-template name="escape-javascript">
      <xsl:with-param name="string" select="ParamList/Param[@name='cacheRoot']"/>
    </xsl:call-template>"; cascadeFieldMap[ "<xsl:value-of select="@paramName"/>" ] = "<xsl:choose>
      <xsl:when test="string-length(ParamList/Param[@name='queryField']) > 0">
        <xsl:call-template name="escape-javascript">
          <xsl:with-param name="string" select="ParamList/Param[@name='queryField']"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="@paramName"/>
      </xsl:otherwise>
    </xsl:choose>"; <xsl:apply-templates
      select="/ContentEditor/ItemContent/DisplayField/Control[ @name='ajax_CascadingDropDown' and ParamList/Param[@name='parent'] = $myName ]"
      mode="buildAjaxDropDownArrays"/>
  </xsl:template>

  <xsl:template match="Control[@name='ajax_CascadingDropDown' and @isReadOnly='no']"
    mode="psxcontrol">

    <xsl:if
      test="string-length( ParamList/Param[ @name = 'parent' ] ) = 0 or ParamList/Param[ @name = 'parent' ] = '0' ">
      <script type="text/javascript"> var cascadeFields_<xsl:value-of
          select="ParamList/Param[@name='cascadeId']"/> = new Array(); var
          cascadeValues_<xsl:value-of select="ParamList/Param[@name='cascadeId']"/> = new
        Array(); <xsl:apply-templates select="." mode="buildAjaxDropDownArrays"/>
      </script>
    </xsl:if>

    <!-- Javascript variable for the null value label-->
    <xsl:variable name="defaultOptionLabel">- Choose -</xsl:variable>
    <xsl:variable name="defaultOptionValue"/>

    <xsl:variable name="dataUrl" select="ParamList/Param[@name='dataUrl']"/>
    <xsl:variable name="currentvalue" select="Value"/>

    <xsl:comment>dataUrl=<xsl:value-of select="$dataUrl"/></xsl:comment>
    <xsl:comment>cascadeId=<xsl:value-of select="ParamList/Param[@name='cascadeId']"/></xsl:comment>
    <xsl:comment>parent=<xsl:value-of select="ParamList/Param[@name='parent']"/></xsl:comment>
    <xsl:comment>cacheRoot=<xsl:value-of select="ParamList/Param[@name='cacheRoot']"/></xsl:comment>

    <select name="{@paramName}"
      onchange="processAjaxDropDowns(this, cascadeFields_{ParamList/Param[@name='cascadeId']}, '')">
      <xsl:call-template name="parametersToAttributes">
        <xsl:with-param name="controlClassName" select="'ajax_CascadingDropDown'"/>
        <xsl:with-param name="controlNode" select="."/>
      </xsl:call-template>
      <!-- We write a null to the db for the "Choose" option -->
      <option>
        <xsl:attribute name="selected"/>
        <xsl:attribute name="value"/>
        <xsl:value-of select="$defaultOptionLabel"/>
      </option>
      <!-- We will let javascript load the opetions for the control at runtime. -->
    </select>

  </xsl:template>

  <xsl:template match="Control[@name='ajax_CascadingDropDown' and @isReadOnly='no']" mode="psxcontrol-body-onload">
    <xsl:if test="string-length( ParamList/Param[ @name = 'parent' ] ) = 0 or ParamList/Param[ @name = 'parent' ] = '0' ">initAjaxDropDowns( cascadeFields_<xsl:value-of select="ParamList/Param[@name='cascadeId']"/>, cascadeValues_<xsl:value-of select="ParamList/Param[@name='cascadeId']"/>, '' ); </xsl:if>
  </xsl:template>

  <xsl:template name="escape-javascript">
    <xsl:param name="string"/>
    <xsl:choose>
      <xsl:when test="contains($string, &quot;&apos;&quot;)">
        <xsl:call-template name="escape-javascript">
          <xsl:with-param name="string"
            select="substring-before($string, &quot;&apos;&quot;)"/>
        </xsl:call-template>
        <xsl:text>\'</xsl:text>
        <xsl:call-template name="escape-javascript">
          <xsl:with-param name="string"
            select="substring-after($string, &quot;&apos;&quot;)"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:when test="contains($string, '&#xA;')">
        <xsl:call-template name="escape-javascript">
          <xsl:with-param name="string" select="substring-before($string, '&#xA;')"/>
        </xsl:call-template>
        <xsl:text>
</xsl:text>
        <xsl:call-template name="escape-javascript">
          <xsl:with-param name="string" select="substring-after($string, '&#xA;')"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:when test="contains($string, '\')">
        <xsl:value-of select="substring-before($string, '\')"/>
        <xsl:text>\\</xsl:text>
        <xsl:call-template name="escape-javascript">
          <xsl:with-param name="string" select="substring-after($string, '\')"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$string"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <!-- End ajax_CascadingDropDown -->

Here’s what I built for my company.

Basically, it works through a combination of JSON-returning applications that you build to retrieve your data, and a series of drop down controls. It uses jQuery for its AJAX calls and is therefore compatible with Rhythmyx 6.5 and up.

Each control that you define in the workbench will control 1 level in a cascade of controls, and you can have multiple cascades on a single editor.

Also, you can choose, as we have done at my office, to pre-cache your hierarchy to reduce database load and improve control response time… the caching scheme it uses is file-system directory based… I can go into more detail if you need it.

file: ajax_cascading.js
create file: Rhythmyx/rx_resources/js/ajax_cascading.js


/*
  AJAX CASCADING FUNCTIONS
  ajax_cascading.js
  
  Sam Rushing, AutoTrader.com, October 24, 2007
  
  This script requires that jQuery.js also be loaded
*/

// GLOBAL VARIABLES

var cascadeUrlMap = new Object();
/*
  cascadeUrlMap [ fieldName1 ] = jsonUrl1;
  cascadeUrlMap [ fieldName2 ] = jsonUrl2;
  ...
  cascadeUrlMap is an object whose properties are named to match the field
  names and whose property values are the coresponding URLs to use to
  retrieve data for that field's level of the cascade.
*/  

var cascadeCacheRootMap = new Object();
/*
  cascadeCacheRootMap [ fieldName1 ] = urlFragment1;
  cascadeCacheRootMap [ fieldName2 ] = urlFragment2;
  ...
  cascadeCacheRootMap is an object whose properties are named to match the field
  names and whose property values are the coresponding root URLs to use to
  retrieve data for that field's level of the cascade.
*/  

var cascadeFieldMap = new Object();
/*
  cascadeFieldMap [ fieldName1 ] = queryField1;
  cascadeFieldMap [ fieldName2 ] = queryField2;
  ...
  cascadeFieldMap is an object whose properties are named to match the field
  names and whose property values are the coresponding query parameter
  names to use to retrieve data for that field's level of the cascade.  
*/

// VARIABLES DEFINED ELSEWHERE
/*
  var cascadeArray = new Array();
  cascadeArray [ 0 ] = fieldName1;
  cascadeArray [ 1 ] = fieldName2;
  ...
  cascadeArray is an array of field names. this array will be used when referencing
  form fields and will also be used as parameter names in ajax calls. The name of
  this array should be different for each set of cascading drop downs in a form and
  the array will be passed in as a parameter to  the functions in this script.
  
  var cascadeValues = newArray();
  cascadeValues [ 0 ] = value1;
  cascadeValues [ 1 ] = value2;
  ...
  cascadeValues is an array of field values that will be set as selected when the
  form loads for the first time on a page.
*/


/*
  getAjaxUrl()
  
  Purpose:
  Builds the url which will be used in the Ajax request. Selected values for previous levels
  of the cascade will be appended as url query parameters to the base url.
  
  Caveats:
  You should make sure that your cgi or target resource interpret the parameters which
  you send as 
*/
function getAjaxUrl( selectObject, cascadeArray )
{
  if ( selectObject != null )
  {
    var cacheUrl = '';
    var url = cascadeUrlMap[ selectObject.name ];
    var params = '';
    
    for ( i = 0; i < cascadeArray.length; i++ )
    {
      if ( cascadeArray[ i ] == selectObject.name )
      {
        break;
      }
      var dest = eval( "document.EditForm." + cascadeArray[i] );
      // Element.extend( dest );
      if (params.length > 0) params += '&';
      var value = dest.options[dest.selectedIndex].value;
      var urlEncodedValue = encodeURIComponent(value)
      var paramname = cascadeFieldMap[ cascadeArray[ i ] ];
      params += paramname + '=' + urlEncodedValue;
      cacheUrl += escape(value) + '/';
    }
    
    var cacheRoot = cascadeCacheRootMap[ cascadeArray[ i ] ];
    
    if ( cacheRoot != null && cacheRoot.length > 0 )
    {
      return cacheRoot + '/' + cacheUrl + url;
    }
    else
    {
      return url + '?' + params;
    }
  }
  return '/';
}

/*
  fillAjaxSelector()
  
  Purpose:
  Makes an AJAX request to retrieve the data for a  <select> element.
  
  Caveats:
  The response from the the url reqest should be of the MIME type "application/javascript".
  A response to the request is expected in the form of:
  
  [
    [ "name", "value" ],
    [ "name", "value" ],
    ...
  ]
*/
function fillAjaxSelector( selectObject, cascadeArray, defaultOptionValue )
{

  var url = getAjaxUrl( selectObject, cascadeArray );
  
  for ( i = 0; i < cascadeArray.length; i++)
  {
    if ( cascadeArray[ i ] == selectObject.name )
    {
      break;
    }
  }
  
  // remove all options from select objects lower in the cascade.
  for ( ; i < cascadeArray.length; i++)
  {
    clearAjaxDropDown( cascadeArray[ i ], defaultOptionValue );
  }
  
  var request = jQuery.getJSON( url, function(json) {
    if ( json != null ) {
      with (selectObject) {
        for ( i = 0; i < json.length; i++ )
        {
          newItem = options.length;
          options[newItem] = new Option( json[ i ][ 0 ] );
          options[newItem].value = json[ i ][ 1 ];
        }
      }
    }
    else
      alert( 'There was an error while trying to load '+url );
  } );
  
} //end of fillAjaxSelector


/*
  clearAjaxDropDown()
  
  Purpose:
  Removes all options from a  <select> element and adds the initial "- Choose -" default option.
  
  Caveats:
  param 'child' can be either a select element object or the name of one.
*/
function clearAjaxDropDown(child, defaultOptionValue)
{
  var dest = ( typeof(child) == 'object' ? child : eval("document.EditForm." + child) );

  with ( dest )
  {
    options.length = 0;
    options[0] = new Option("- Choose -");
    
    if(defaultOptionValue != null)
    {
      options[0].value = defaultOptionValue;
    }
    else
    {
      //For now, preserve the old behavior if the defaultOptionValue param is not supplied
      options[0].value = "0";
    }
  }
} //end clearAjaxDropDown

                
/*
  clearAjaxDropDown()
  
  Purpose:
  Fill the immediate child of the selectedDropdown and clear all of the descendants
*/
function processAjaxDropDowns( selectedDropdown, cascadeArray, defaultOptionValue )
{
  // find out where the selected drop down is in the list
  for ( i = 0; i < cascadeArray.length; i++)
  {
    if ( cascadeArray[ i ] == selectedDropdown.name )
    {
      i++;
      break;
    }
  }
  
  // populate its child and clear its grandchildren.
  if ( i < cascadeArray.length )
  {
    var dest = eval( "document.EditForm." + cascadeArray[i] );
    fillAjaxSelector( dest, cascadeArray, defaultOptionValue );
  }
}


function _initAjaxDropDown ( objectName, currentValue_enc, url_enc )
{
  var selectObject = eval( "document.EditForm." + objectName  );
  var currentValue = unescape(currentValue_enc);
  var url = unescape(url_enc);
  
  var request = jQuery.getJSON( url, function(json) {
    if ( json != null ) {
      with ( selectObject ) {
        for ( i = 0; i < json.length; i++ )
        {
          newItem = options.length;
          options[newItem] = new Option( json[ i ][ 0 ] );
          options[newItem].value = json[ i ][ 1 ];
          if ( json[ i ][ 1 ] == currentValue )
          {
            options[newItem].selected = true;
          }
        }
      }
    }
    else
      alert( 'There was an error while trying to load '+url );
  });
}


/*
  initAjaxDropDowns()
  
  Purpose:
  Load the initial set of options for all fields in a cascade that currently have a value, and
  data for the first child in the cascasde which does not have a value
*/
function initAjaxDropDowns( cascadeArray, cascadeValues, defaultOptionValue )
{
  for ( i = 0; i < cascadeValues.length; i++ )
  {
    var currentValue = cascadeValues[ i ];
    
    var url = '';
    var cacheRoot = cascadeCacheRootMap[ cascadeArray[ i ] ];
    
    if ( cacheRoot != null && cacheRoot.length > 0 )
    {
      url = cacheRoot + '/';
      for ( j = 0; j < i; j++ )
      {
        url += cascadeValues[ j ] + '/';
      }
      url += cascadeUrlMap[ cascadeArray[ i ] ];
    }
    else
    {
      url = cascadeUrlMap[ cascadeArray[ i ] ];
      var params = "";
      for ( j = 0; j < i; j++ )
      {
        if (params.length > 0) params += '&';
        var value = cascadeValues[ j ];
        var urlEncodedValue = encodeURIComponent(value)
        var paramname = cascadeFieldMap[ cascadeArray[ j ] ];
        params += paramname + '=' + urlEncodedValue;
      }
      url = url + '?' + params;
    }
    
    setTimeout( '_initAjaxDropDown( "' +cascadeArray[ i ] + '","' + escape(currentValue) +'","' +escape(url)+'")', 1 );
    
    if ( currentValue.length == 0 )
    {
      break;
    }
  }
}

Just to clarify, I built my control, below, for 5.7, and it continues to work in 6.5. If there is an AJAX cascade built by percussion for 6.5, you should probably go that route, if you can.

What is this control called ? We use version 6.7.0.

It is a custom control called “PSODynamicDropDown” which uses jQuery.

I just added this control to code.percussion.com. If you want to check it out, see
this thread.

-nate

I am using this control now but there seems to be a problem with it:

When I choose a value in the dropdown and close the window, the display of the chosen value in the content editor (“Edit Content” popup screen) is empty. I am using an xml application as my datasource (the xml application goes against a database to retrieve values based on a where clause dictated by another drop down field).

Is there a bug in the control when xml application is used as datasource ?

Have you , under the " Choices" tab , selected " Retrieve from xml application " and pointed it to the xml application you have used?

Yes I have. The dropdown itself gets populated correctly. The problem is after I save it.

Here is the situation:
The dynamic dropdown is in a child table. I click on ‘Edit table’ and select a value in the drop down. Then I click ‘Update’ and ‘Close’ thus returning to the parent window ( “Edit Content”). Now, the value for the field shows a blank.
I know the value does get saved internally since when I publish, I see it in the output but the display does not show the value selected.

The DropDown was not written to be used in child tables. As per the engineer who developed the code

" It would require more work to do this. it posts the current selected field value to the update url resource. this works differently for child tables as there are
multiple rows for the same column. So essentially you have multiple sets of dropdowns. in the main form, multiple sets are supported by specifying a group name
on the controls, the default group name is “default”, may be something can be done with this by calculating a unique group name using an id for the row, but
there may be other complications. "

The code is available and one approach could be for you to make the necessary changes, as this code was never a part of the product .

I tried it with regular fields for a content type (ie did not use any child tables). Still same problem: The dropdown allows me to select a value but when I update and close and try to View content, i don’t see the selected value - the field name shows up but the value section is blank.

Any suggestions ?

K . Is it possible to open up a tech support ticket ?

I just opened a ticket. Lets see what they say. I am just surprised that nobody has noticed this problem - unless I am doing something totally wrong.