Skip to content

Commit 671aca3

Browse files
committed
Merge branch 'release/3.2.0'
2 parents d14c1b1 + 40df03a commit 671aca3

10 files changed

+381
-108
lines changed

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22
All notable changes to the Form Render Skip Logic module will be documented in this file.
33
This project adheres to [Semantic Versioning](http://semver.org/).
44

5+
## [3.2.0] - 2018-08-08
6+
### Added
7+
- Add support for migrating FRSL v2.x.x configurations to FRSL v3.x.x configurations automatically (Dileep Rajput)
8+
- Add support for equations on Advanced control mode (Tiago Bember Simeao)
9+
- Add support to event-relative control fields (Tiago Bember Simeao)
10+
- Add Zenodo DOI to README (Philip Chase)
11+
12+
513
## [3.1.1] - 2018-06-04
614
### Changed
715
- Fixing control field's 2nd column visibility. (Tiago Bember Simeao)

ExternalModule.php

+167-89
Large diffs are not rendered by default.

Migration.php

+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
<?php
2+
3+
namespace FormRenderSkipLogic\Migration;
4+
5+
use ExternalModules\ExternalModules;
6+
7+
class Migration {
8+
9+
private $PREFIX;
10+
11+
function __construct($prefix) {
12+
$this->PREFIX = $prefix;
13+
}
14+
15+
/**
16+
* checks if FRSL settings for specified version exist. Does not support version
17+
* 1. Will return false for any invalid version.
18+
* @param string $version, module version in REDCap format e.g. 'v3.1.1'
19+
* @return boolean, true if they exist, false otherwise
20+
*/
21+
function checkIfVersionSettingsExist($version) {
22+
$module_id = $this->getFRSLModuleId();
23+
24+
$q = "SELECT 1 FROM redcap_external_module_settings WHERE external_module_id='$module_id' AND `key` IN ";
25+
26+
if (preg_match("/v2\.[0-9]+(\.[0-9]+)?/", $version)) {
27+
$q .= "('control_field', 'event_name', 'event_name', 'field_name', 'enabled_before_ctrl_field_is_set', 'target_instruments', 'instrument_name', 'control_field_value')";
28+
} else if (preg_match("/v3\.[0-9]+(\.[0-9]+)?/", $version)) {
29+
$q .= "('control_fields', 'control_mode', 'control_event_id', 'control_field_key', 'control_piping', 'control_default_value', 'branching_logic', 'condition_operator', 'condition_value', 'target_forms', 'target_events_select', 'target_events')";
30+
} else {
31+
return false;
32+
}
33+
34+
$result = ExternalModules::query($q);
35+
36+
//if we got something return true, otherwise false
37+
$settings_exist = !empty($result->fetch_assoc());
38+
39+
return $settings_exist;
40+
}
41+
42+
/**
43+
* gets external module_id for FRSL.
44+
* cannot use ExternalModules::getIdForPrefix() because it is private.
45+
*/
46+
function getFRSLModuleId() {
47+
$q = "SELECT external_module_id FROM redcap_external_modules where directory_prefix = '" . $this->PREFIX . "'" ;
48+
$result = ExternalModules::query($q);
49+
$id = $result->fetch_assoc()['external_module_id'];
50+
51+
return $id;
52+
}
53+
54+
/**
55+
* gets FRSLv2.x.x settings for each as an associative array indexed by
56+
* project_id, where each project setting is an associative array indexed by
57+
* setting name and maps to a setting value
58+
* @return array $settings
59+
*/
60+
function getV2Settings() {
61+
$module_id = $this->getFRSLModuleId();
62+
63+
//get old settings data
64+
$q = "SELECT project_id, `key`, value FROM redcap_external_module_settings WHERE external_module_id='$module_id' AND `key` IN ('control_field', 'event_name', 'event_name', 'field_name', 'enabled_before_ctrl_field_is_set', 'target_instruments', 'instrument_name', 'control_field_value')";
65+
$result = ExternalModules::query($q);
66+
67+
//create data stucture to represent old settings data
68+
$settings = [];
69+
while($row = $result->fetch_assoc()) {
70+
$project_id = $row["project_id"];
71+
$key = $row["key"];
72+
$value = $row["value"];
73+
$settings[$project_id][$key] = $value;
74+
}
75+
76+
return $settings;
77+
}
78+
79+
/**
80+
* converts settings from from FRSL_v2.X to FRSL_v3.X
81+
* @param array $old_settings, array of v2 settings indexed by project_id
82+
* @return array $new_settings, array of v3 settings indexed by project_id
83+
*/
84+
function convertV2SettingsToV3Settings($old_settings) {
85+
$new_settings = [];
86+
foreach ($old_settings as $project_id => $old_setting) {
87+
88+
//generate branching_logic settings using old values
89+
$old_instrument_names = json_decode($old_setting["instrument_name"]);
90+
$old_instrument_count = count($old_instrument_names);
91+
$old_control_field_values = json_decode($old_setting["control_field_value"]);
92+
$branching_logic = [];
93+
$condition_operator = [];
94+
$condition_value = [];
95+
$target_forms = [];
96+
$target_events_select = [];
97+
$target_events = [];
98+
99+
for($i = 0; $i < $old_instrument_count; $i++) {
100+
//check if a branching_logic setting already exists for this instrument
101+
$index = array_search($old_control_field_values[$i], $condition_value);
102+
103+
if($index !== false) {
104+
//append instrument to existing branching_logic configuration
105+
$target_forms[$index][] = $old_instrument_names[$i];
106+
} else {
107+
//create a new branching_logic sub setting
108+
$branching_logic[] = true;
109+
$condition_operator[] = null;
110+
$condition_value[] = $old_control_field_values[$i];
111+
$target_forms[] = [$old_instrument_names[$i]];
112+
$target_events_select[] = false;
113+
$target_events[] = [null];
114+
}
115+
}
116+
117+
//convert to nested JSON-arrays for storage
118+
$branching_logic = "[" . json_encode($branching_logic) . "]";
119+
$condition_operator = "[" . json_encode($condition_operator) . "]";
120+
$condition_value = "[" . json_encode($condition_value) . "]";
121+
$target_forms = "[" . json_encode($target_forms) . "]";
122+
$target_events_select = "[" . json_encode($target_events_select) . "]";
123+
$target_events = "[" . json_encode($target_events) . "]";
124+
125+
//create sub-structure for project setting
126+
$setting = [];
127+
128+
$setting["control_fields"] = $old_setting["control_field"];
129+
$setting["control_mode"] = '["default"]';
130+
$setting["control_event_id"] = $old_setting["event_name"];
131+
$setting["control_field_key"] = $old_setting["field_name"];
132+
$setting["control_piping"] = "[null]";
133+
$setting["control_default_value"] = "[null]";
134+
$setting["branching_logic"] = $branching_logic;
135+
$setting["condition_value"] = $condition_value;
136+
$setting["condition_operator"] = $condition_operator;
137+
$setting["target_forms"] = $target_forms;
138+
$setting["target_events_select"] = $target_events_select;
139+
$setting["target_events"] = $target_events;
140+
141+
//store in main data structure
142+
$new_settings[$project_id] = $setting;
143+
}
144+
145+
return $new_settings;
146+
147+
}
148+
149+
/**
150+
* stores FRSL_v3.X created by convert2XSettingsTo3XSettings method into db
151+
* @param array $settings, array of v3 settings indexed by project_id and
152+
* maps to a array of setting keys pointing to their associated values.
153+
*/
154+
function storeV3Settings($settings) {
155+
$module_id = $this->getFRSLModuleId();
156+
157+
foreach ($settings as $project_id => $setting) {
158+
$q = "INSERT INTO redcap_external_module_settings (external_module_id, project_id, `key`, type, value) VALUES ";
159+
160+
//build query
161+
$values_to_insert = [];
162+
foreach ($setting as $key => $value) {
163+
$values_to_insert[] = "($module_id, $project_id, '$key', 'json-array', '$value')";
164+
}
165+
166+
$q .= join(",", $values_to_insert);
167+
ExternalModules::query($q);
168+
}
169+
}
170+
}
171+
172+
?>

README.md

+14-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# REDCap Form Render Skip Logic (FRSL)
22

3+
[![DOI](https://zenodo.org/badge/97271445.svg)](https://zenodo.org/badge/latestdoi/97271445)
4+
35
This REDCap module hides and shows instruments based on the values of REDCap form fields i.e. a branching logic for instruments.
46

57
## Motivation
@@ -24,9 +26,18 @@ See the original functional specification at [https://docs.google.com/document/d
2426
## Configuration
2527
Access **Manage External Modules** section of your project, click on Form Render Skip Logic's configure button, and save settings in order to show or hide instruments according to your needs.
2628

27-
The top level entry in the configuration is a Control Field. A control field is described by either an event name and a field name _or_ a smart variable _or_ piped data. Each control field can govern the display of a set of forms. You can define multiple control fields as long as each controls a separate set of forms.
29+
The top level entry in the configuration is a Control Field. A control field is described by either:
30+
31+
1. a selected event-field pair _or_
32+
2. a text that describes an equation, working as a [calculated field](https://www.ctsi.ufl.edu/files/2017/06/Calculated-Fields-%E2%80%93-REDCap-How.pdf) (you may use Piping and smart variables)
33+
34+
![advanced control field](img/advanced_control_field.png)
2835

29-
Each control field can have multiple conditions. Each condition compares the control field to a string or number. If the condition evaluates as true, the forms listed under the condition will be displayed. If the condition is false and no other true condition displays them, the forms will be hidden. Be careful that the values in the conditions of a control field are mutually exclusive or the results could be unexpected.
36+
Each control field can govern the display of a set of forms. You can define multiple control fields as long as each controls a separate set of forms.
37+
38+
Each control field can have multiple conditions. Each condition compares the control field to a string or number. If the condition evaluates as true, the forms listed under the condition will be displayed. If the condition is false and no other true condition displays them, the forms will be hidden.
39+
40+
Obs.: if multiple conditions are applied to the same instrument, the form is displayed if *at least* one of the conditions is met.
3041

3142
All forms _not_ named under a condition will be displayed at all times. Optionally, each condition can specify a list of events that restrict the behavior of this rule.
3243

@@ -40,10 +51,4 @@ See [Animal Identification Example](samples/Animal_Identification.md) for a work
4051

4152
## Upgrading From Version 2.x - 3.x
4253

43-
Note that version 3.0.0 introduced a breaking change in the configuration. To execute the upgrade you will need to follow these steps:
44-
45-
* Note the projects that use FRSL
46-
* Record the configuration of each project
47-
* Erase the configuration of each project
48-
* Upgrade FRSL to 3.x
49-
* Re-enter the configuration for each project
54+
Note that version 3.0.0 introduced a breaking change in the configuration. When you upgrade to version 3.x all of your old configurations in 2.x will be converted into the 3.x configuration scheme. This migration only occurs the first time you upgrade from 2.x to 3.x . Thereafter, if you decided to switch back and forth between the two versions, your configurations will not transfer. This is to ensure that all of your old 2.x configurations will still be available to you if you decide to go back to version 2.x .

config.json

+11-8
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
"namespace": "FormRenderSkipLogic\\ExternalModule",
44
"description": "This module hides and shows instruments based on the values of REDCap form fields. Multiple control fields can be defined to control the display of non-overlapping sets of forms.",
55
"permissions": [
6+
"redcap_every_page_before_render",
67
"redcap_every_page_top",
78
"redcap_data_entry_form_top",
89
"redcap_survey_page_top",
9-
"redcap_save_record"
10+
"redcap_save_record",
11+
"redcap_module_system_change_version",
12+
"redcap_module_system_enable"
1013
],
1114
"authors": [
1215
{
@@ -63,24 +66,24 @@
6366
},
6467
{
6568
"value": "advanced",
66-
"name": "Advanced (Piping)"
69+
"name": "Advanced (Equation with Piping and/or Smart Variables)"
6770
}
6871
]
6972
},
7073
{
71-
"key": "control_event_id",
74+
"key": "control_field_key",
7275
"name": "Field",
73-
"type": "event-list"
76+
"type": "field-list"
7477
},
7578
{
76-
"key": "control_field_key",
79+
"key": "control_event_id",
7780
"name": "Field",
78-
"type": "field-list"
81+
"type": "event-list"
7982
},
8083
{
8184
"key": "control_piping",
82-
"name": "Piping",
83-
"type": "text"
85+
"name": "Equation / Piping",
86+
"type": "textarea"
8487
},
8588
{
8689
"key": "control_default_value",

css/config.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#external-modules-configure-modal.frsl tr[field="control_field_key"],
1+
#external-modules-configure-modal.frsl tr[field="control_event_id"],
22
#external-modules-configure-modal.frsl tr[field="condition_value"] {
33
position: absolute;
44
right: 10000px;

css/piping-helper.css

+7
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,10 @@
1515
top: -1px;
1616
margin-right: 2px;
1717
}
18+
19+
.frsl-piping-helper a {
20+
font-weight: normal;
21+
color: #3089d4;
22+
font-size: 11px;
23+
margin: 10px 0 0 10px;
24+
}

img/advanced_control_field.png

36.4 KB
Loading

img/configuration_form.png

-8.89 KB
Loading

js/config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ $(document).ready(function() {
6666
$(selectorShow).parent().parent().show();
6767
$(selectorHide).parent().parent().hide();
6868

69-
place2ndColumnField($('[name="control_field_key' + suffix + '"]').parent().parent());
69+
place2ndColumnField($('[name="control_event_id' + suffix + '"]').parent().parent());
7070
};
7171

7272
var $checkboxes = $modal.find('tr[field="target_events_select"] .external-modules-input-element');

0 commit comments

Comments
 (0)