Skip to content

Commit

Permalink
script refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
Damion Dooley committed Mar 22, 2022
1 parent 021a2ed commit 8b9f0c6
Show file tree
Hide file tree
Showing 5 changed files with 868 additions and 853 deletions.
16 changes: 10 additions & 6 deletions main.html
Original file line number Diff line number Diff line change
Expand Up @@ -585,13 +585,17 @@ <h5>Are you ready to harmonize your contextual data?</h5>
<script src="libraries/popper.min.js"></script>
<script src="libraries/bootstrap.min.js"></script>
<script src="libraries/handsontable.full.min.js"></script>
<script type="text/javascript" src="libraries/shim.min.js"></script>
<script type="text/javascript" src="libraries/xlsx.full.min.js"></script>
<script type="text/javascript" src="libraries/chosen.jquery.min.js"></script>
<script type="text/javascript" src="libraries/moment.min.js"></script>
<script type="text/javascript" src="libraries/jquery-ui.min.js"></script>
<script type="text/javascript" src="libraries/FileSaver.min.js"></script>
<script src="libraries/shim.min.js"></script>
<script src="libraries/xlsx.full.min.js"></script>
<script src="libraries/chosen.jquery.min.js"></script>
<script src="libraries/moment.min.js"></script>
<script src="libraries/jquery-ui.min.js"></script>
<script src="libraries/FileSaver.min.js"></script>
<script src="script/export_utils.js"></script>
<script src="script/validation.js"></script>
<script src="script/file_utils.js"></script>
<script src="script/field_rules.js"></script>
<!-- script src="template/menu.js"></script -->
<script src="script/main.js"></script>
</body>
</html>
320 changes: 320 additions & 0 deletions script/field_rules.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,320 @@
/**
* Iterate through rules set up for named columns
* Like matrixFieldChangeRules but this is triggered by a single change
* by a user edit on a field cell. This creates complexity for fields that
* work together, e.g. either of first two fields of
* [field][field unit][field bin] could have been focus of change.
*
* @param {Array} change array [row, col, ? , value]
* @param {Object} fields See `data.js`.
* @param {Array} triggered_changes array BY REFERENCE. One or more changes is
* appended to this.
*/
const fieldChangeRules = (change, fields, triggered_changes) => {

const row = change[0];
const col = change[1];
const field = fields[col];

// Test field against capitalization change.
if (field.capitalize !== null && change[3] && change[3].length > 0)
change[3] = changeCase(change[3], field.capitalize);

// Rules that require a particular column following and/or preceeding
// current one.
if (fields.length > col+1) {

// We're prepping a SPARSE ARRAY here for binChangeTest()
var matrix = [0];
matrix[0] = {}; // Essential for creating sparse array.
matrix[0][col] = change[3]; // prime changed value

const prevName = (col > 0) ? fields[col-1].fieldName : null;
const nextName = (fields.length > col+1) ? fields[col+1].fieldName : null;

// Match <field>[field unit]
if (nextName === field.fieldName + ' unit') {

if (field.datatype === 'xs:date') {

// Transform ISO 8601 date to bin year / month granularity.
// "day" granularity is taken care of by regular date validation.
// Don't attempt to reformat x/y/z dates here.
const dateGranularity = window.HOT.getDataAtCell(row, col+1);
// previously had to block x/y/z with change[3].indexOf('/') === -1 &&
if (dateGranularity === 'year' || dateGranularity === 'month') {
change[3] = setDateChange(dateGranularity, change[3]);
}
return;
}

// Match <field>[field unit][field bin]
const nextNextName = (fields.length > col+2) ? fields[col+2].fieldName : null;
if (nextNextName === field.fieldName + ' bin') {

matrix[0][col+1] = window.HOT.getDataAtCell(row, col+1); //prime unit
binChangeTest(matrix, row, col, fields, 2, triggered_changes);
return;
}
}

// Match <field>[field bin]
if (nextName === field.fieldName + ' bin') {
binChangeTest(matrix, row, col, fields, 1, triggered_changes);
return;
}

/*
const value = matrix[row][col];
// For IMPORT, this is only run on fields that have a value.
// Note matrix pass cell by reference so its content can be changed.
if (value && value.length > 0) {
// Do parseFloat rather than parseInt to accomodate fractional bins.
let number = parseFloat(value);
var selection = '';
if (number >= 0) {
*/
// Match [field]<field unit>
if (field.fieldName === prevName + ' unit') {

// Match [field]<field unit>[field bin]
if (prevName + ' bin' === nextName) {
// trigger reevaluation of bin from field
matrix[0][col-1] = window.HOT.getDataAtCell(row, col-1);
binChangeTest(matrix, row, col-1, fields, 2, triggered_changes);
return;
}


// Match previous field as date field
// A change from month to year or day to month/year triggers new
// date value
if (fields[col-1].datatype === 'xs:date' && (change[3] === 'year' || change[3] === 'month') ) {

let dateString = window.HOT.getDataAtCell(row, col-1);
// If there is a date entered, adjust it
// previously had to block x/y/z with && dateString.indexOf('/') === -1
if (dateString) {
dateString = setDateChange(change[3], dateString);
matrix[0][col-1] = dateString;
triggered_changes.push([row, col-1, undefined, dateString]);
}
return;
}
}

}

};

/**
* Modify a string to match specified case.
* @param {String} val String to modify.
* @param {String} capitalize Case to modify string to; one of `'UPPER'`,
* `'lower'` or `'Title'`.
* @return {String} String with modified case.
*/
const changeCase = (val, capitalize) => {
switch (capitalize) {
case 'lower':
val = val.toLowerCase();
break;
case 'UPPER':
val = val.toUpperCase();
break;
case 'Title':
val = val.split(' ').
map(w => w[0].toUpperCase() + w.substr(1).toLowerCase()).
join(' ');
break;

}
return val

};

/**
* Modify matrix data for grid according to specified rules.
* This is useful when calling `hot.loadData`, as cell changes from said method
* are not recognized by `afterChange`.
* @param {Array<Array<String>>} matrix Data meant for grid.
* @param {Object} hot Handsontable instance of grid.
* @param {Object} data See `data.js`.
* @return {Array<Array<String>>} Modified matrix.
*/
const matrixFieldChangeRules = (matrix, hot, data) => {
const fields = getFields(data);
for (let col=0; col < fields.length; col++) {

const field = fields[col];

// Test field against capitalization change.
if (field.capitalize !== null) {
for (let row=0; row < matrix.length; row++) {
if (!matrix[row][col]) continue;
matrix[row][col] = changeCase(matrix[row][col], field.capitalize);
}
}

var triggered_changes = [];

// Rules that require a column or two following current one.
if (fields.length > col+1) {
const nextFieldName = fields[col+1].fieldName;

// Rule: for any "x bin" field label, following a "x" field,
// find and set appropriate bin selection.
if (nextFieldName === field.fieldName + ' bin') {
binChangeTest(matrix, 0, col, fields, 1, triggered_changes);
}
// Rule: for any [x], [x unit], [x bin] series of fields
else
if (nextFieldName === field.fieldName + ' unit') {
if (fields[col].datatype === 'xs:date') {
//Validate
for (let row=0; row < matrix.length; row++) {
if (!matrix[row][col]) continue;
const dateGranularity = matrix[row][col + 1];
if (dateGranularity === 'year' || dateGranularity === 'month') {
matrix[row][col] = setDateChange(dateGranularity, matrix[row][col]);
}
}
}
else if (fieldUnitBinTest(fields, col)) {
// 2 specifies bin offset
binChangeTest(matrix, 0, col, fields, 2, triggered_changes);
}
}
}

// Do triggered changes:
for (const change of triggered_changes) {
matrix[change[0]][change[1]] = change[3];
}
}

return matrix;
}

/**
* Adjust given dateString date to match year or month granularity given by
* dateGranularity parameter. If month unit required but not supplied, then
* a yyyy-__-01 will be supplied to indicate that month needs attention.
*
* @param {String} dateGranularity, either 'year' or 'month'
* @param {String} ISO 8601 date string or leading part, possibly just YYYY or
YYYY-MM
* @return {String} ISO 8601 date string.
*/
const setDateChange = (dateGranularity, dateString, dateBlank='__') => {

var dateParts = dateString.split('-');
// Incomming date may have nothing in it.
if (dateParts[0].length > 0) {
switch (dateGranularity) {
case 'year':
dateParts[1] = '01';
dateParts[2] = '01';
break
case 'month':
if (!dateParts[1])
dateParts[1] = dateBlank; //by default triggers date validation error
dateParts[2] = '01';
break;
default:
// do nothing
}
}
// Update changed value (note "change" object overrides triggered_changes)
return dateParts.join('-');

}

/**
* Test to see if col's field is followed by [field unit],[field bin] fields
* @param {Object} fields See `data.js`.
* @param {Integer} column of numeric field.
*/
const fieldUnitBinTest = (fields, col) => {
return ((fields.length > col+2)
&& (fields[col+1].fieldName == fields[col].fieldName + ' unit')
&& (fields[col+2].fieldName == fields[col].fieldName + ' bin'));
}

/**
* Test [field],[field bin] or [field],[field unit],[field bin] combinations
* to see if bin update needed.
* @param {Array<Array<String>>} matrix Data meant for grid.
* @param {Integer} rowOffset
* @param {Integer} col column of numeric field.
* @param {Object} fields See `data.js`.
* @param {Integer} binOffset column of bin field.
* @param {Array} triggered_changes array of change which is appended to changes.
*/
const binChangeTest = (matrix, rowOffset, col, fields, binOffset, triggered_changes) => {
for (let row in matrix) {
const value = matrix[row][col];
// For IMPORT, this is only run on fields that have a value.
// Note matrix pass cell by reference so its content can be changed.
if (value && value.length > 0) {
// Do parseFloat rather than parseInt to accomodate fractional bins.
let number = parseFloat(value);

var selection = '';
if (number >= 0) {
// Here we have the 3 field call, with units sandwitched in the middle
if (binOffset === 2) {
const unit = matrix[row][col+1];
// Host age unit is interpreted by default to be year.
// If user selects month, value is converted into years for binning.
// Future solution won't hardcode month / year assumption
if (unit) {
if (unit === 'month') {
number = number / 12;
}
}
// Force unit to be year if empty.
//else {
// triggered_changes.push([rowOffset + parseInt(row), col+1, undefined, 'year']);
//}

}
// .flatVocabulary is an array of string bin ranges e.g. "10 - 19"
for (const number_range of fields[col+binOffset].flatVocabulary) {
// ParseInt just looks at first part of number
if (number >= parseFloat(number_range)) {
selection = number_range;
continue;
}
break;
}
triggered_changes.push([rowOffset + parseInt(row), col+binOffset, undefined, selection]);
}
else {
/*
REVISION: Keep host age bin data separate from host age.
// Integer/date field is a textual value, possibly a metadata 'Missing'
// etc. If bin field has a value, leave it unchanged; but if it doesn't
// then populate bin with input field metadata status too.
const bin_value = matrix[row][col+binOffset]; //
selection = bin_value; // Default value is itself.
const bin_values = fields[col+binOffset].flatVocabulary;
if (!bin_value || bin_value === '' && value in bin_values) {
selection = value
}
*/
// If a unit field exists, then set that to metadata too.
if (binOffset == 2) {
const unit_value = matrix[row][col+1]; //window.HOT.getDataAtCell(rowOffset, col+1);
const unit_values = fields[col+1].flatVocabulary;
if (!unit_value || unit_value === '' && value in unit_values) {
triggered_changes.push([rowOffset + parseInt(row), col+1, undefined, value]);
}
}
}

}
}
}
Loading

0 comments on commit 8b9f0c6

Please sign in to comment.