I believe I’ve done something recently that may achieve what you’re looking for.
I rebuilt our catalog output (for printing) this year, and in doing so updated the data structures which make it happen. In the end, I ended up making the following linked fields:
- Catalog Sections: Related Group, taken from a list of existing groups in LiveWhale.
- Catalog Sections: Linked Courses (x4), taken from a list of course codes in data from our SIS.
- Requirements: Related Sections (x4), taken from the existing Catalog Sections in LiveWhale.


The solution I arrived at requires a custom module and a little development work. Be warned, it is also a bit “hacky”, but it appears to work so long as there’s only one short string to save per field. In brief:
- In a profile type, edit the type and add a new custom profile field(s) with the type “text (one line)”. Save and note the custom field’s id.
- In a custom module, define new behavior with the “onOutput” handler function.
- In the function, start by using $LW->read() to pull the data you want to include in the field.
- Modify the data as needed and, for each option, determine what will be the value (the entity’s id) and display text (the entity’s name).
- Search the buffer for the field(s) to edit and replace their text <input/> with a <select/> element which includes all of the options.
Since I was doing this for nine fields, I wrote a utility function to handle the replacement step (#5):
/*
Utility function to replace a profile custom field, type text, into a <select/> field.
- $buffer is the variable that can be modified in certain LW handlers, like onOutput();
- $options is expected to be an array of value->name pairs to build the select <options/>.
- $field_ids is expected to be an array where the values are custom field ids to alter.
- Return: the edited $buffer variable
*/
function replaceTextWithSelect($buffer, $options, $field_ids) {
/* This attempts to update a custom text field in a profile with a <select/> field using given id->name pairs.
In the profile type editor, the field should be a <input type="text"/> field.
This will replace the <input/> with a <select/> element, with the value being saved and recalled.
*/
// build our new set of options
$select_options = '<option value=""></option>'; // default, empty
foreach ($options as $value => $name) {
$select_options .= '<option value="'.$value.'">'.$name.'</option>'."\n";
}
// for each field to edit
foreach($field_ids as $field_id) {
// identify the current value of the field, if it has one
// $match[0] is whole <input/>, $match [1] is value number only, no finds should be NULL
$match = array();
preg_match('/<input type="text".*id="profiles_custom_'.$field_id.'".*value="(.*)".*>/U', $buffer, $match, PREG_UNMATCHED_AS_NULL);
$current = empty($match) ? -1 : $match[1]; // avoids "undefined offset" if no match found
// in the field's own copy of the options, mark the current option if there is one
$regex_search = '/(<option value="'.$current.'")(>.*<\/option>)/U';
$regex_replace = '$1 selected="selected"$2';
$field_options = preg_replace($regex_search, $regex_replace, $select_options);
// replace the custom field <input> with the <select/> and options
$regex_search = '/<input type="text".*id="profiles_custom_'.$field_id.'".*>/U';
$regex_replace = '<select class="form-control was-text" name="custom_'.$field_id.'" id="profiles_custom_'.$field_id.'">'.$field_options.'</select>';
$buffer = preg_replace($regex_search, $regex_replace, $buffer);
}
// return our edited $buffer
return $buffer;
}
Then, for each profile type and set of its fields that share the same data, code like the following can be defined within the “onOutput” handler:
/*
Editing profiles of the Catalog Section type (55).
- Replace profile custom text field with a select with group value->name pairs.
*/
if ($_LW->page=='profiles_edit' && $_LW->_GET['tid']==55) {
// configuration variables
$field_ids = [310]; // Related Group field
// get all groups
$data_type = 'groups';
$args= ['paginate' => 999];
$groups = $_LW->read($data_type, $args);
$groups = (isset($groups['results'])) ? $groups['results'] : $groups; // API change?
// for each group, build an array of value->name pairs
$options = [];
foreach ($groups as $group) {
$gid = $group['id'];
$gname = $group['title'];
// optional: further filter/edit the per item data as needed
$options[$gid] = $gname;
}
// sort the list by name
natcasesort($options);
// call helper function to make the edits to the buffer
$buffer = replaceTextWithSelect($buffer, $options, $field_ids);
}
Some notes I have from our module:
- Wanted to avoid JS solutions, if possible.
- Client custom fields are appended after the onOutput handler, meaning we can’t use them here. This functionality may be limited to only custom profile fields.
- Making a text field a select field is odd, but the underlying text field (or database column) appears willing to store and recall our similar input of a single string (the selected option’s value). In the end, the change is practically cosmetic.
- We avoid using profile custom field types with “pre-defined” values (select, radio, etc), which are prone to either being hard to read (id) or prone to desync (name).
Hope that helps you and others. Also hope this solution isn’t too sacrilegious, lol.
Let me know if you have any questions (or see anything to improve).