Skip to content

Commit

Permalink
Fixed masked input. Constraint support in numeric inputs. Use xsd-dat…
Browse files Browse the repository at this point in the history
…atype for numeric input determining. Bumped version to 0.0.7.
  • Loading branch information
ledsoft committed Aug 29, 2016
1 parent d14e4f1 commit ad93655
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 35 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "semforms",
"version": "0.0.6",
"version": "0.0.7",
"description": "Semantic forms generator and processor",
"keywords": [
"react",
Expand All @@ -25,7 +25,7 @@
"moment": "^2.14.1",
"kbss-react-bootstrap-datetimepicker": "https://dev.inbas.cz/dist/kbss-react-bootstrap-datetimepicker-0.0.4.tgz",
"react-bootstrap-typeahead": "https://dev.inbas.cz/dist/react-bootstrap-typeahead-0.0.7.tgz",
"jsonld-utils": "https://kbss.felk.cvut.cz/dist/jsonld-utils-0.0.2.tgz",
"jsonld-utils": "https://kbss.felk.cvut.cz/dist/jsonld-utils-0.0.3.tgz",
"inputmask-core": "^2.1.1"
},
"peerDependencies": {
Expand Down
92 changes: 78 additions & 14 deletions src/components/answer/InputAnswer.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,81 @@
'use strict';

import React from 'react';
import JsonLdUtils from 'jsonld-utils';
import React from "react";
import assign from "object-assign";
import JsonLdUtils from "jsonld-utils";
import Configuration from "../../model/Configuration";
import Constants from "../../constants/Constants";
import FormUtils from "../../util/FormUtils";

import Configuration from '../../model/Configuration';
import Constants from '../../constants/Constants';
import FormUtils from '../../util/FormUtils';
import Utils from '../../util/Utils';
const NUMERIC_DATATYPES = [Constants.XSD.INT, Constants.XSD.INTEGER, Constants.XSD.NON_NEGATIVE_INTEGER,
Constants.XSD.NON_POSITIVE_INTEGER, Constants.XSD.NEGATIVE_INTEGER, Constants.XSD.POSITIVE_INTEGER];

const NUMBER_RULES = {};
NUMBER_RULES[Constants.XSD.NON_NEGATIVE_INTEGER] = {min: 0};
NUMBER_RULES[Constants.XSD.NON_POSITIVE_INTEGER] = {max: 0};
NUMBER_RULES[Constants.XSD.NEGATIVE_INTEGER] = {max: -1};
NUMBER_RULES[Constants.XSD.POSITIVE_INTEGER] = {min: 1};

class InputPropertiesResolver {

static _resolveInputType(question, value) {
if (FormUtils.isTextarea(question, value)) {
return 'textarea';
} else if (InputPropertiesResolver._isNumeric(question)) {
return 'number';
} else {
return 'text';
}
}

static _isNumeric(question) {
for (var i = 0, len = NUMERIC_DATATYPES.length; i < len; i++) {
if (JsonLdUtils.hasValue(question, Constants.HAS_DATATYPE, NUMERIC_DATATYPES[i])) {
return true;
}
}
return false;
}

static resolveInputProperties(question, value) {
var props = {};
props['type'] = InputPropertiesResolver._resolveInputType(question, value);
switch (props['type']) {
case 'textarea':
props['rows'] = 5;
break;
case 'number':
assign(props, InputPropertiesResolver._resolveNumberRestrictions(question));
break;
default:
props['disabled'] = FormUtils.isDisabled(question);
break;
}
return props;
}

static _resolveNumberRestrictions(question) {
var restriction = {};
Object.getOwnPropertyNames(NUMBER_RULES).forEach(key => {
if (JsonLdUtils.hasValue(question, Constants.HAS_DATATYPE, key)) {
assign(restriction, NUMBER_RULES[key]);
}
});
if (question[Constants.XSD.MIN_INCLUSIVE] !== undefined) {
restriction['min'] = question[Constants.XSD.MIN_INCLUSIVE];
}
if (question[Constants.XSD.MIN_EXCLUSIVE] !== undefined) {
restriction['min'] = question[Constants.XSD.MIN_EXCLUSIVE] + 1;
}
if (question[Constants.XSD.MAX_EXCLUSIVE] !== undefined) {
restriction['max'] = question[Constants.XSD.MAX_EXCLUSIVE] - 1;
}
if (question[Constants.XSD.MAX_INCLUSIVE] !== undefined) {
restriction['max'] = question[Constants.XSD.MAX_INCLUSIVE];
}
return restriction;
}
}

const InputAnswer = (props) => {
var question = props.question,
Expand All @@ -17,17 +86,12 @@ const InputAnswer = (props) => {
if (answer[Constants.HAS_OBJECT_VALUE] && answer[Constants.HAS_OBJECT_VALUE][JsonLdUtils.RDFS_LABEL]) {
value = JsonLdUtils.getJsonAttValue(answer[Constants.HAS_OBJECT_VALUE], JsonLdUtils.RDFS_LABEL);
}
return React.createElement(Configuration.inputComponent, {
type: Utils.resolveInputType(question, value),
return React.createElement(Configuration.inputComponent, assign({}, InputPropertiesResolver.resolveInputProperties(question, value), {
label: props.label,
title: props.title,
value: value,
onChange: (e) => {
props.onChange(e.target.value)
},
disabled: FormUtils.isDisabled(question),
rows: 5
});
onChange: (e) => props.onChange(e.target.value)
}));
};

InputAnswer.propTypes = {
Expand Down
3 changes: 2 additions & 1 deletion src/components/answer/MaskedInputAnswer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React from 'react';
import JsonLdUtils from 'jsonld-utils';

import Constants from '../../constants/Constants';
import FormUtils from '../../util/FormUtils';
import InputAnswer from './InputAnswer';
import Logger from '../../util/Logger';
import MaskedInput from '../MaskedInput';
Expand All @@ -18,7 +19,7 @@ const MaskedInputAnswer = (props) => {
return <InputAnswer {...props}/>;
}
return <MaskedInput mask={mask} value={value} label={props.label} title={props.title} placeholder={props.label}
onChange={(e) => props.onChange(e.target.value)}/>;
onChange={(e) => props.onChange(e.target.value)} disabled={FormUtils.isDisabled(question)}/>;
};

MaskedInputAnswer.propTypes = {
Expand Down
26 changes: 24 additions & 2 deletions src/constants/Constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const FORM = 'http://onto.fel.cvut.cz/ontologies/documentation/form',
HAS_VALUE_TYPE = 'http://onto.fel.cvut.cz/ontologies/form/has-value-type',
IS_DISABLED = 'http://onto.fel.cvut.cz/ontologies/aviation/form-376/is-disabled',
INPUT_MASK = 'http://onto.fel.cvut.cz/ontologies/form/has-input-mask',
HAS_DATATYPE = 'http://onto.fel.cvut.cz/ontologies/form/has-datatype',
LAYOUT_CLASS = 'http://onto.fel.cvut.cz/ontologies/form-layout/has-layout-class',
LAYOUT = {
FORM: 'form',
Expand All @@ -22,7 +23,6 @@ const FORM = 'http://onto.fel.cvut.cz/ontologies/documentation/form',
DISABLED: 'disabled',
HIDDEN: 'hidden',
TEXTAREA: 'textarea',
NUMBER: 'number',
DATE: 'date',
TIME: 'time',
DATETIME: 'datetime',
Expand All @@ -38,7 +38,21 @@ const FORM = 'http://onto.fel.cvut.cz/ontologies/documentation/form',
HAS_ANSWER_ORIGIN = 'http://onto.fel.cvut.cz/ontologies/form/has-answer-origin',

HAS_DATA_VALUE = 'http://onto.fel.cvut.cz/ontologies/documentation/has_data_value',
HAS_OBJECT_VALUE = 'http://onto.fel.cvut.cz/ontologies/documentation/has_object_value';
HAS_OBJECT_VALUE = 'http://onto.fel.cvut.cz/ontologies/documentation/has_object_value',

XSD = {
MAX_EXCLUSIVE: 'http://www.w3.org/2001/XMLSchema#maxExclusive',
MAX_INCLUSIVE: 'http://www.w3.org/2001/XMLSchema#maxInclusive',
MIN_EXCLUSIVE: 'http://www.w3.org/2001/XMLSchema#minExclusive',
MIN_INCLUSIVE: 'http://www.w3.org/2001/XMLSchema#minInclusive',

INT: 'http://www.w3.org/2001/XMLSchema#int',
INTEGER: 'http://www.w3.org/2001/XMLSchema#integer',
NEGATIVE_INTEGER: 'http://www.w3.org/2001/XMLSchema#negativeInteger',
NON_NEGATIVE_INTEGER: 'http://www.w3.org/2001/XMLSchema#nonNegativeInteger',
NON_POSITIVE_INTEGER: 'http://www.w3.org/2001/XMLSchema#nonPositiveInteger',
POSITIVE_INTEGER: 'http://www.w3.org/2001/XMLSchema#positiveInteger'
};

/**
* Contains mainly definition of constants used to parse the form declaration.
Expand Down Expand Up @@ -88,6 +102,10 @@ export default class Constants {
return INPUT_MASK;
}

static get HAS_DATATYPE() {
return HAS_DATATYPE;
}

static get LAYOUT_CLASS() {
return LAYOUT_CLASS;
}
Expand Down Expand Up @@ -123,4 +141,8 @@ export default class Constants {
static get HAS_OBJECT_VALUE() {
return HAS_OBJECT_VALUE;
}

static get XSD() {
return XSD;
}
}
4 changes: 0 additions & 4 deletions src/util/FormUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,6 @@ export default class FormUtils {
return JsonLdUtils.hasValue(question, Constants.LAYOUT_CLASS, Constants.LAYOUT.CHECKBOX);
}

static isNumericInput(question) {
return JsonLdUtils.hasValue(question, Constants.LAYOUT_CLASS, Constants.LAYOUT.NUMBER);
}

static isMaskedInput(question) {
return JsonLdUtils.hasValue(question, Constants.LAYOUT_CLASS, Constants.LAYOUT.MASKED_INPUT);
}
Expand Down
10 changes: 0 additions & 10 deletions src/util/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,6 @@ export default class Utils {
}
}

static resolveInputType(question, value) {
if (FormUtils.isTextarea(question, value)) {
return 'textarea';
} else if (FormUtils.isNumericInput(question)) {
return 'number';
} else {
return 'text';
}
}

/**
* Resolves which format of date/time/datetime value should be used in the datetime picker.
* @param question Question with format info
Expand Down
2 changes: 1 addition & 1 deletion test/__tests__/AnswerTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ describe('Answer component', () => {
};
answer[Constants.HAS_DATA_VALUE] = value;
question[Constants.HAS_ANSWER] = [answer];
question[Constants.LAYOUT_CLASS].push(Constants.LAYOUT.NUMBER);
question[Constants.HAS_DATATYPE] = Constants.XSD.INT;
var component = Environment.render(<Answer answer={answer} question={question} onChange={onChange}/>),

input = TestUtils.findRenderedDOMComponentWithTag(component, 'input');
Expand Down
127 changes: 127 additions & 0 deletions test/__tests__/InputAnswerTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
'use strict';

import React from "react";
import TestUtils from "react-addons-test-utils";
import JsonLdUtils from "jsonld-utils";
import Answer from "../../src/components/Answer";
import Configuration from "../../src/model/Configuration";
import Constants from "../../src/constants/Constants";
import Environment from "../environment/Environment";
import Generator from "../environment/Generator";

const LABEL = 'Input answer test';

describe('InputAnswer', () => {

var question,
answer,
onChange;

beforeEach(() => {
question = {
"@id": Generator.getRandomUri()
};
question[Constants.LAYOUT_CLASS] = [];
question[JsonLdUtils.RDFS_LABEL] = {
"@language": "en",
"@value": LABEL
};
question[JsonLdUtils.RDFS_COMMENT] = {
"@language": "en",
"@value": "Javascript sucks!!!"
};
onChange = jasmine.createSpy('onChange');
Configuration.intl = {
locale: 'en'
};
answer = {
"id": Generator.getRandomUri()
};
question[Constants.HAS_ANSWER] = [answer];
});

it('sets min on numeric input when xsd:minInclusive is used in question', () => {
var min = 100,
value = 117;
question[Constants.HAS_DATATYPE] = Constants.XSD.INT;
question[Constants.XSD.MIN_INCLUSIVE] = min;
answer[Constants.HAS_DATA_VALUE] = value;

var component = Environment.render(<Answer question={question} answer={answer} onChange={onChange}/>),

input = TestUtils.findRenderedDOMComponentWithTag(component, 'input');
expect(input.type).toEqual('number');
expect(input.min).toEqual(min.toString());
});

it('sets min on numeric input when xsd:minExclusive is used in question', () => {
var min = 100,
value = 117;
question[Constants.HAS_DATATYPE] = Constants.XSD.INT;
question[Constants.XSD.MIN_EXCLUSIVE] = min;
answer[Constants.HAS_DATA_VALUE] = value;

var component = Environment.render(<Answer question={question} answer={answer} onChange={onChange}/>),

input = TestUtils.findRenderedDOMComponentWithTag(component, 'input');
expect(input.type).toEqual('number');
expect(input.min).toEqual((min + 1).toString());
});

it('sets max on numeric input when xsd:maxExclusive is used in question', () => {
var max = 1000,
value = 117;
question[Constants.HAS_DATATYPE] = Constants.XSD.INT;
question[Constants.XSD.MAX_EXCLUSIVE] = max;
answer[Constants.HAS_DATA_VALUE] = value;

var component = Environment.render(<Answer question={question} answer={answer} onChange={onChange}/>),

input = TestUtils.findRenderedDOMComponentWithTag(component, 'input');
expect(input.type).toEqual('number');
expect(input.max).toEqual((max - 1).toString());
});

it('sets max on numeric input when xsd:maxInclusive is used in question', () => {
var max = 1000,
value = 117;
question[Constants.HAS_DATATYPE] = Constants.XSD.INT;
question[Constants.XSD.MAX_INCLUSIVE] = max;
answer[Constants.HAS_DATA_VALUE] = value;

var component = Environment.render(<Answer question={question} answer={answer} onChange={onChange}/>),

input = TestUtils.findRenderedDOMComponentWithTag(component, 'input');
expect(input.type).toEqual('number');
expect(input.max).toEqual(max.toString());
});

it('sets both min and max on numeric input when both are used in question', () => {
var max = 1000,
min = 100,
value = 117;
question[Constants.HAS_DATATYPE] = Constants.XSD.INT;
question[Constants.XSD.MAX_INCLUSIVE] = max;
question[Constants.XSD.MIN_INCLUSIVE] = min;
answer[Constants.HAS_DATA_VALUE] = value;

var component = Environment.render(<Answer question={question} answer={answer} onChange={onChange}/>),

input = TestUtils.findRenderedDOMComponentWithTag(component, 'input');
expect(input.type).toEqual('number');
expect(input.max).toEqual(max.toString());
expect(input.min).toEqual(min.toString());
});

it('sets min when xsd:positiveInteger is used as question datatype', () => {
var value = 117;
question[Constants.HAS_DATATYPE] = Constants.XSD.POSITIVE_INTEGER;
answer[Constants.HAS_DATA_VALUE] = value;

var component = Environment.render(<Answer question={question} answer={answer} onChange={onChange}/>),

input = TestUtils.findRenderedDOMComponentWithTag(component, 'input');
expect(input.type).toEqual('number');
expect(input.min).toEqual('1');
});
});
19 changes: 18 additions & 1 deletion test/__tests__/MaskedInputAnswerTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ describe('MaskedInputAnswer', () => {

it('renders a regular input when question contains no mask', () => {
var value = '08/2016',
mask = '11/1111',
answer = {
'@id': Generator.getRandomUri()
};
Expand All @@ -38,4 +37,22 @@ describe('MaskedInputAnswer', () => {
input = TestUtils.findRenderedDOMComponentWithTag(component, 'input');
expect(input.value).toEqual(value);
});

it('render disabled masked input with value when disabled layout class is specified', () => {
var value = '08/2016',
mask = '11/1111',
answer = {
'@id': Generator.getRandomUri()
};
answer[Constants.HAS_DATA_VALUE] = value;
question[Constants.HAS_ANSWER] = [answer];
question[JsonLdUtils.RDFS_LABEL] = 'Test';
question[Constants.INPUT_MASK] = mask;
question[Constants.LAYOUT_CLASS] = [Constants.LAYOUT.MASKED_INPUT, Constants.LAYOUT.DISABLED];
var component = Environment.render(<Answer answer={answer} question={question} onChange={onChange}/>),

input = TestUtils.findRenderedDOMComponentWithTag(component, 'input');
expect(input.value).toEqual(value);
expect(input.disabled).toBeTruthy();
});
});

0 comments on commit ad93655

Please sign in to comment.