Skip to content
This repository has been archived by the owner on Nov 2, 2023. It is now read-only.

Commit

Permalink
Merge pull request #171 from clint-tseng/cxlt/157
Browse files Browse the repository at this point in the history
all/new #157: add range widget.
  • Loading branch information
lognaturel committed Jul 25, 2017
2 parents 4f0f8fe + 233ad34 commit 5b455bb
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 17 deletions.
56 changes: 54 additions & 2 deletions public/javascripts/control.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
;(function($)
{
var validationNS = odkmaker.namespace.load('odkmaker.validation');
var controlNS = odkmaker.namespace.load('odkmaker.control');

// Globally active singleton facilities:
var $propertyList = $('.propertyList');
Expand Down Expand Up @@ -500,7 +501,7 @@
value: {},
summary: false } },
inputNumeric: {
range: { name: 'Range',
range: { name: 'Valid Range',
type: 'numericRange',
description: 'Valid numeric range for the user input of this control.',
tips: [
Expand All @@ -514,14 +515,50 @@
tips: [ 'It is also displayed if a custom constraint check is failed.' ],
value: {},
summary: false },
appearance: { name: 'Style',
type: 'enum',
description: 'Style of collection interface to present.',
tips: [ 'A Picker, also known as a Spinner, is a little textbox with plus and minus buttons to change the number.' ],
options: [ 'Textbox',
'Slider',
'Vertical Slider',
'Picker' ],
value: 'Textbox',
summary: true },
kind: { name: 'Kind',
type: 'enum',
bindDisplayIf: { appearance: 'Textbox' },
description: 'Type of number accepted.',
tips: [ 'In some data collection tools, this will affect the type of keypad shown.' ],
options: [ 'Integer',
'Decimal' ],
value: 'Integer',
summary: true } },
summary: true },
selectRange:{ name: 'Selectable Range',
type: 'numericRange',
validation: [ 'rangeRequired' ],
bindDisplayIf: { appearance: [ 'Slider', 'Vertical Slider', 'Picker' ] },
optional: false,
inclusivity: false,
description: 'The lowest and highest selectable values, inclusive.',
tips: [ 'Integers and decimals are both valid.' ],
value: { min: "1", max: "10" },
summary: false },
selectStep: { name: 'Selectable Range: Step Between Choices',
type: 'text',
validation: [ 'required', 'numeric', 'stepDivision' ],
bindDisplayIf: { appearance: [ 'Slider', 'Vertical Slider', 'Picker' ] },
description: 'The gap between adjacent selectable values in the range.',
tips: [ 'The step must cleanly end at the low and high ends of the range; in other words, it must divide the selectable range perfectly.' ],
value: '1',
summary: false },
sliderTicks:{ name: 'Slider Ticks',
type: 'bool',
bindDisplayIf: { appearance: [ 'Slider', 'Vertical Slider' ] },
description: 'The lowest and highest selectable values, inclusive.',
tips: [ 'Integers and decimals are both valid.' ],
value: true,
summary: false } },
inputDate: {
range: { name: 'Range',
type: 'dateRange',
Expand Down Expand Up @@ -775,4 +812,19 @@
}
};


controlNS.upgrade = {
2: function(form) {
var processControl = function(control)
{
if (_.isArray(control.children))
_.each(control.children, processControl);

if ((control.type === 'inputNumeric') && (control.appearance == null))
control.appearance = 'Textbox';
};
_.each(form.controls, processControl);
}
};

})(jQuery);
39 changes: 39 additions & 0 deletions public/javascripts/core.validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -291,9 +291,48 @@
apply();
});
});

// if the property has a bindDisplayIf we'll automagically create subscriptions
// to track that binding and not bother validating if the property is hidden.
var displayed = true;
if (property.bindDisplayIf != null)
{
// TODO: duplicated from property-editor.js. if this logic continues to
// evolve we should consolidate. but js has no tuple type so it's not obvious
// how to do so.
var bdiKey, bdiValues;
if (_.isString(property.bindDisplayIf))
{
bdiKey = property.bindDisplayIf;
bdiValues = [ true ];
}
else
{
bdiKey = _.keys(property.bindDisplayIf)[0];
bdiValues = property.bindDisplayIf[bdiKey];
if (!_.isArray(bdiValues)) { bdiValues = [ bdiValues ]; }
}

subscribe($control, 'self', 'property', bdiKey, function(value)
{
displayed = _.contains(bdiValues, value);
apply();
});
}

var lastHasError = false;
var apply = function()
{
if (displayed === false)
{
if (lastHasError === true)
{
lastHasError = result.hasError = false;
$control.trigger('odkControl-validationChanged', [ property, false ]);
}
return;
}

var passed;
if ((validationObj.prereq != null) && (validationObj.prereq.apply(null, params) !== true))
passed = true; // bail out if we fail the precondition.
Expand Down
32 changes: 26 additions & 6 deletions public/javascripts/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ var dataNS = odkmaker.namespace.load('odkmaker.data');
// the current will be upgraded. to define an upgrade, add an upgrade object to any module
// whose keys are the number of the version to be upgraded to and values are the functions
// that take the form data and update it to conform with that version.
odkmaker.data.currentVersion = 1;
odkmaker.data.currentVersion = 2;
odkmaker.data.load = function(formObj)
{
var version = formObj.metadata.version || 0;
Expand Down Expand Up @@ -134,7 +134,9 @@ var dataNS = odkmaker.namespace.load('odkmaker.data');
'Manual (No GPS)': 'placement-map',
'Minimal (spinner)': 'minimal',
'Table': 'label',
'Horizontal Layout': 'horizontal'
'Horizontal Layout': 'horizontal',
'Vertical Slider': 'vertical',
'Picker': 'picker'
};
var addTranslation = function(obj, itextPath, translations)
{
Expand Down Expand Up @@ -392,10 +394,26 @@ var dataNS = odkmaker.namespace.load('odkmaker.data');
binding.attrs.type = 'string';
else if (control.type == 'inputNumeric')
{
if (control.kind == 'Integer')
binding.attrs.type = 'int';
else if (control.kind == 'Decimal')
binding.attrs.type = 'decimal';
if (control.appearance == 'Textbox')
{
if (control.kind == 'Integer')
binding.attrs.type = 'int';
else if (control.kind == 'Decimal')
binding.attrs.type = 'decimal';
}
else
{
// overrides extant input tag with a range tag.
bodyTag.name = 'range';
if (_.isObject(control.selectRange))
{
bodyTag.attrs.start = control.selectRange.min;
bodyTag.attrs.end = control.selectRange.max;
}
bodyTag.attrs.step = control.selectStep;
var step = parseFloat(control.selectStep);
binding.attrs.type = (Math.floor(step) === step) ? 'int' : 'decimal';
}
}
else if (control.type == 'inputDate')
{
Expand Down Expand Up @@ -513,6 +531,8 @@ var dataNS = odkmaker.namespace.load('odkmaker.data');
}
if ((control.type === 'inputDate') && ((control.kind === 'Year and Month') || (control.kind === 'Year')))
bodyTag.attrs.appearance = (control.kind === 'Year') ? 'year' : 'month-year';
if (control.sliderTicks === false)
bodyTag.attrs.appearance = ((bodyTag.attrs.appearance || '') + ' no-ticks').trim();

// options
if (control.options !== undefined)
Expand Down
39 changes: 36 additions & 3 deletions public/javascripts/impl.validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,26 +79,33 @@
var hasOptions = function(val) { return _.isArray(val) && (val.length > 0); };
var xmlLegalChars = function(val) { return /^[0-9a-z_.-]+$/i.test(val); };
var alphaStart = function(val) { return /^[a-z]/i.test(val); };
var isNumeric = function(val) { return /^[+-]?\d*(\.\d*)?$/.test(val) && /\d/.test(val); };

// the actual definitions:
validationNS.validations = {
required: {
given: [ 'self' ],
check: function(self) { return hasString(self); },
check: hasString,
message: 'This property is required.'
},
xmlLegalChars: {
given: [ 'self' ],
prereq: hasString,
check: function(self) { return xmlLegalChars(self); },
check: xmlLegalChars,
message: 'Only letters, numbers, -, _, and . are allowed.'
},
alphaStart: {
given: [ 'self' ],
prereq: hasString,
check: function(self) { return alphaStart(self); },
check: alphaStart,
message: 'The first character must be a letter.'
},
numeric: {
given: [ 'self' ],
prereq: hasString,
check: isNumeric,
message: 'Please enter a number.'
},
unique: {
given: [ 'self', { scope: 'all', property: 'self' } ],
prereq: hasString,
Expand Down Expand Up @@ -147,6 +154,32 @@
},
message: 'One or more Underlying Value is longer than the allowed maximum of 32 characters.'
},
// checks both presence and numericness. i couldn't think of a case you wouldn't want both.
rangeRequired: {
given: [ 'self' ],
check: function(range)
{
return _.isObject(range) && isNumeric(range.min) && isNumeric(range.max);
},
message: 'Please enter two valid numbers.'
},
stepDivision: {
given: [ 'self', { scope: 'self', property: 'selectRange' } ],
prereq: function(step, range)
{
return _.isObject(range) && _.all([ step, range.min, range.max ], isNumeric);
},
check: function(step, range)
{
// can't use % because js float nastiness.
var step = parseFloat(step);
if (step <= 0) return false;

var quotient = (parseFloat(range.max) - parseFloat(range.min)) / step;
return Math.floor(quotient) === quotient;
},
message: 'Step must divide the selectable range perfectly into evenly-sized increments.',
},
hasOptions: {
given: [ 'self' ],
prereq: _.isArray,
Expand Down
28 changes: 23 additions & 5 deletions public/javascripts/property-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,25 @@

if (property.bindDisplayIf != null)
{
var parentProperty = $parent.data('odkControl-properties')[property.bindDisplayIf];
var showHide = function() { $this.toggle(parentProperty.value !== false); }
$parent.on('odkControl-propertiesUpdated', function(_, propId)
// normalize two formats: 'key' just checks bool on that key.
// { 'key': [ 'value1', 'value2' ] } checks a single property against values.
// someday if required we can check multiple properties.
var key, values;
if (_.isString(property.bindDisplayIf))
{
if (propId === property.bindDisplayIf) showHide();
});
key = property.bindDisplayIf;
values = [ true ];
}
else
{
key = _.keys(property.bindDisplayIf)[0];
values = property.bindDisplayIf[key];
if (!_.isArray(values)) { values = [ values ]; }
}

var parentProperty = $parent.data('odkControl-properties')[key];
var showHide = function() { $this.toggle(_.contains(values, parentProperty.value)); }
$parent.on('odkControl-propertiesUpdated', function(_, propId) { if (propId === key) showHide(); });
showHide();
}
});
Expand Down Expand Up @@ -119,6 +132,11 @@
};
};

if (property.optional === false)
$editor.addClass('nonOptional');
if (property.inclusivity === false)
$editor.addClass('hideInclusivity');

if ((property.value === false) || (property.value == null))
$inputs.attr('disabled', true);
else
Expand Down
5 changes: 4 additions & 1 deletion public/stylesheets/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -1137,7 +1137,10 @@ body > .control.last .controlFlowArrow,
content: '\e900';
}

.property-length .inclusiveCtrl, .property-count .inclusiveCtrl {
.nonOptional .editorEnabled {
display: none;
}
.hideInclusivity .inclusiveCtrl, .property-length .inclusiveCtrl, .property-count .inclusiveCtrl {
display: none;
}

Expand Down

0 comments on commit 5b455bb

Please sign in to comment.