Skip to content
Merged
68 changes: 68 additions & 0 deletions ExternalModule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php
/**
* @file
* Provides ExternalModule class for Auto Populate Fields.
*/

namespace AutoPopulateFields\ExternalModule;

use ExternalModules\AbstractExternalModule;
use ExternalModules\ExternalModules;

require_once 'includes/helper.php';

/**
* ExternalModule class for Auto Populate Fields.
*/
class ExternalModule extends AbstractExternalModule {

/**
* @inheritdoc
*/
function hook_every_page_top($project_id) {
$features = auto_populate_fields_get_available_features();

$js_vars = array();
$js_files = array('js/helper.js');

foreach ($features as $feature) {
include_once 'includes/' . $feature . '.php';

$function = 'auto_populate_fields_' . $feature;
if ($settings = $function()) {
$js_vars[$feature] = $settings;
$js_files[] = 'js/' . $feature . '.js';
}
}

if ($js_vars) {
// Set up js variables.
$this->initJsVars($js_vars);
}

// Loads js files.
$this->loadJsFiles($js_files);
}

/**
* Loads js files.
*
* @param array $js_files
* An array of js files paths within the module.
*/
function loadJsFiles($js_files) {
foreach ($js_files as $file) {
echo '<script src="' . $this->getUrl($file) . '"></script>';
}
}

/**
* Loads js variables.
*
* @param array $varss
* An array of js variables to set up.
*/
function initJsVars($vars) {
echo '<script> var autoPopulateFields = ' . json_encode($vars) . ';</script>';
}
}
29 changes: 27 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,27 @@
# auto_populate_fields
This redcap modules enables the projects to auto populate fields.
# Auto populate field values
This REDCap Module provides tools to autopopulate fields.

## Prerequisites
- [REDCap Modules](https://github.com/vanderbilt/redcap-external-modules)

## Installation
- Clone this repo into to `<redcap-root>/modules/auto_populate_fields_v1.0`.
- Go to **Control Center > Manage External Modules** and enable Auto Populate Fields.
- For each project you want to use this module, go to the project home page, click on **Manage External Modules** link, and then enable Auto Populate Fields for that project.

## Features included
This module provides 3 new [action tags](https://wiki.chpc.utah.edu/pages/viewpage.action?pageId=595001400):
- `@DEFAULT-FROM-FIELD`
- `@DEFAULT-WHEN-VISIBLE`
- `@DEFAULT-FROM-PREVIOUS-EVENT`

### @DEFAULT-FROM-FIELD
It allows users to set up a field's default value from an existing field on the same form. Use case examples:
- Using hidden fields as source for visible fields, e.g. `@DEFAULT-FROM-FIELD="hidden_first_name"`.
- When a form field has been populated in the backend by a DET or API call - `@DEFAULT` cannot handle this.

### @DEFAULT-WHEN-VISIBLE
If the field is visible it sets the initial value otherwise it removes the value. This is mainly useful in fields which are visible and hidden by branching logic, e.g. `@DEFAULT-FROM-FIELD="10"`.

### @DEFAULT-FROM-PREVIOUS-EVENT
Sets a field's default value based on its own value in a previous event. To map the default value from another field, you may specify the source as a parameter to the action tag, e.g `@DEFAULT-FROM-PREVIOUS-EVENT="source_field"`.
55 changes: 55 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"name": "Auto Populate Fields",
"namespace": "AutoPopulateFields\\ExternalModule",
"description": "This extension automatically populates field values if proper action tag is given.",
"permissions": [
"hook_every_page_top"
],
"authors": [
{
"name": "Philip Chase",
"email": "pbc@ufl.edu",
"institution": "CTS-IT - University of Florida"
},
{
"name": "Taryn Stoffs",
"email": "tls@ufl.edu",
"institution": "CTS-IT - University of Florida"
},
{
"name": "Surya Prasanna",
"email": "suryayalla@ufl.edu",
"institution": "CTS-IT - University of Florida"
},
{
"name": "Prasad Lanka",
"email": "planka@ufl.edu",
"institution": "CTS-IT - University of Florida"
},
{
"name": "Dileep Rajput",
"email": "rajputd@ufl.edu",
"institution": "CTS-IT - University of Florida"
},
{
"name": "Stewart Wehmeyer",
"email": "swehmeyer@ufl.edu",
"institution": "CTS-IT - University of Florida"
},
{
"name": "Tiago Bember",
"email": "tbembersimeao@ufl.edu",
"institution": "CTS-IT - University of Florida"
},
{
"name": "Mike Conlon, Ph.D.",
"email": "mconlon@ufl.edu",
"institution": "University of Florida"
},
{
"name": "Carl J Pepine, M.D., MACC",
"email": "",
"institution": "University of Florida"
}
]
}
52 changes: 52 additions & 0 deletions includes/default_from_field.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php
/**
* @file
* Provides Default From Field feature.
*/

require_once 'helper.php';

/**
* Handles @DEFAULT-FROM-FIELD action tag.
*/
function auto_populate_fields_default_from_field() {
global $Proj;

$mappings = array();
foreach (auto_populate_fields_get_fields_names() as $target_field_name) {
$target_field_info = $Proj->metadata[$target_field_name];
if (!$source_field_name = auto_populate_fields_action_tag_semaphore($target_field_info['misc'], '@DEFAULT-FROM-FIELD')) {
continue;
}

// Checking if source field exists.
if (empty($Proj->metadata[$source_field_name])) {
continue;
}

// Handling checkbox case.
if ($target_field_info['element_type'] == 'checkbox') {
$target_field_name = '__chkn__' . $target_field_name;
}

// Aux function.
$getFormElementSelector = function($element_type, $element_name) {
return '#questiontable ' . ($element_type == 'select' ? 'select' : 'input') . '[name="' . $element_name . '"]';
};

// Setting up target info.
$source_field_info = $Proj->metadata[$source_field_name];
$mappings[$target_field_name] = array(
'type' => $target_field_info['element_type'],
'selector' => $getFormElementSelector($target_field_info['element_type'], $target_field_name),
'source' => $getFormElementSelector($source_field_info['element_type'], $source_field_name),
);
}

if (empty($mappings)) {
// If no mappings, there is no reason to proceed.
return;
}

return array('mappings' => $mappings);
};
57 changes: 57 additions & 0 deletions includes/default_from_previous_event.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php
/**
* @file
* Provides Default from Previous Event feature.
*/

require_once 'helper.php';

/**
* Handles @DEFAULT-FROM-PREVIOUS-EVENT action tag.
*/
function auto_populate_fields_default_from_previous_event() {
global $Proj;

$action_tag = '@DEFAULT-FROM-PREVIOUS-EVENT';
$targets = array();

foreach (auto_populate_fields_get_fields_names() as $field_name) {
$misc = $Proj->metadata[$field_name]['misc'];
if (auto_populate_fields_action_tag_semaphore($misc, $action_tag, false)) {
if (!$source_field = Form::getValueInQuotesActionTag($misc, $action_tag)) {
// If no value is provided on the action tag, set the same
// field as source by default.
$source_field = $field_name;
}

$targets[$field_name] = $source_field;
}
}

if (empty($targets)) {
return;
}

$data = REDCap::getData($Proj->project['project_id'], 'array', $_GET['id']);
if (empty($data)) {
return;
}

$data = $data[$_GET['id']];
$arm = $Proj->eventInfo[$_GET['event_id']]['arm_num'];
$events = array_keys($Proj->events[$arm]['events']);

foreach ($targets as $field_target => $field_source) {
$default_value = null;

foreach ($events as $event) {
if (!empty($data[$event]) && isset($data[$event][$field_source])) {
$default_value = $data[$event][$field_source];
}
}

if (!is_null($default_value)) {
$Proj->metadata[$field_target]['misc'] .= ' @DEFAULT="' . $default_value . '"';
}
}
}
107 changes: 107 additions & 0 deletions includes/default_when_visible.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php
/**
* @file
* Provides Default When Visible feature.
*/

require_once 'helper.php';

/**
* Handles @DEFAULT-WHEN-VISIBLE action tag.
*/
function auto_populate_fields_default_when_visible() {
global $Proj;

/*
* Populate forward_map, this actually has the key,value pair of the fields present in branching logic
* and backward_map has the reverse relationship. These are used to breakdown the branching logic and
* create the listener on the parent fields.
*/
$add_default_mappings = array();
$forward_map = array();
$backward_map = array();

foreach (auto_populate_fields_get_fields_names() as $field_name) {
$field_info = $Proj->metadata[$field_name];
if (!$default_value = auto_populate_fields_action_tag_semaphore($field_info['misc'], '@DEFAULT-WHEN-VISIBLE')) {
continue;
}

// constructs the backward map from branching logic data
$branching_logic = $field_info['branching_logic'];
preg_match_all("/\[([^\]]*)\]/", $branching_logic, $matches);

$branch_array = array();
foreach ($matches[1] as $mat1) {
$pos = strpos($mat1, '(');
if ($pos) {
$branch_array[] = substr($mat1, 0, $pos);
} else {
$branch_array[] = $mat1;
}
}
$backward_map[$field_name] = $branch_array;

// if field has mutiple options or choices to choose, this gets the array of all the values.
$options = array();
if ($field_info['element_enum']) {
foreach (explode("\\n", $field_info['element_enum']) as $tuple) {
list($key, ) = explode(',', $tuple);
$options[] = trim($key);
}
}

//construct parent_field_name and selector for each field so that it is easy for the js
// to reference them than creating the string each time.
$parent_field_name = "";
$selector = ($field_info['element_type'] == 'select' ? 'select' : 'input');
if ($field_info['element_type'] == 'checkbox') {
$parent_field_name = '__chkn__' . $field_name;
$selector .= '[name="' . $parent_field_name . '"]';
} else if ($field_info['element_type'] == 'radio') {
$parent_field_name = $field_name . '___radio';
$selector .= ':radio[name="' . $parent_field_name . '"]';
} else {
$selector .= '[name="' . $field_name . '"]';
$parent_field_name = $field_name;
}

// finally collect all the information related to a field in the form of a map
$add_default_mappings[$field_name] = array(
'id' => "#" . $field_name . '-tr',
'parent_field_name' => $parent_field_name,
'selector' => $selector,
'element_type' => $field_info['element_type'],
'options' => json_encode($options),
'branching_logic' => $field_info['branching_logic'],
'default_value' => $default_value
);
}

// construct forward map from backward map.
foreach ($backward_map as $key => $value) {
foreach ($value as $subkey => $sub_value) {
if (array_key_exists($sub_value, $forward_map)) {
if (!in_array($key, $forward_map[$sub_value])) {
$forward_map[$sub_value][] = $key;
}
} else {
$forward_map[$sub_value] = array();
$forward_map[$sub_value][] = $key;
}
}
}

// if not fields are eligible for this action tag them simply return from here.
if (empty($add_default_mappings)) {
// If no mappings, there is no reason to proceed.
return;
}

// variables that are required by the js are stored returned form this function
$returnVal = array();
$returnVal['add_default_mappings'] = $add_default_mappings;
$returnVal['forward_map'] = $forward_map;

return $returnVal;
}
Loading