Using Vue to help form validation with weight for age

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
1
down vote

favorite












I am refactoring a very old web page which calculates bolus and infusion drug doses for children from premature through to young adulthood.



One of the most common drug errors is accidental 10 fold under or overdosing. Given the form needs both weight and age to produce an appropriate chart, it seemed wise to look up statistical data and work out the centile (cumulative standard normal distribution) of weight for age and apply soft and hard limits, with a checkbox saying that values exceeding the soft limits have been double checked. The full code (including the helper functions referenced below) is on GitHub.



The questions are:



  • Is the extensive use of wrapping data p_varName & computed getters & setters boilerplate for each 'watched' variable the best approach, so that any time age, gender, weight or gestation change, the centile (+/- warnings as required) are updated?

  • Is it the correct thing to have the centile range [bootstrap] alert as a component? This will not be reused, but it felt cleaner to compartmentalise the code/objectives. Is there a better way to achieve this separation?

  • Is emitting the 'validCentile' event when validity changes the best way - or should I use a slot with a 'valid' property on the parent scope (weightage)?

  • All the data properties are 'flat' - i.e. nothing nested - would readability/debugability/general finesse be improved by nesting properties?

Any thoughts (including those not related to the points above) very much appreciated.



<!-- src/components/weightage.vue -->
<template>
<div class="weightAge">
<fieldset class="form-group">
<div class="form-row">
<legend class="col-form-label col-sm-2 pt-0">Gender</legend>
<div class="col-sm-10 gender">
<div class="form-check form-check-inline" id="male">
<input type="radio" name="gender" id="maleRadio" :value="true" class="form-check-input" v-model="isMale" />
<label class="form-check-label" for="maleRadio">
Male
</label>
</div>
<div class="form-check form-check-inline" id="female">
<input type="radio" name="gender" id="femaleRadio" :value="false" class="form-check-input" v-model="isMale" />
<label class="form-check-label" for="femaleRadio">
Female
</label>
</div>
</div>
</div>
</fieldset>
<div class="form-group form-row">
<label class="col-sm-2 col-form-label" for="Weight" >Weight</label>
<div class="input-group col-sm-10">
<input id="Weight" type=number min="0.2" max="400" class="form-control" v-model.number="weight" required />
<div class="input-group-append">
<div class="input-group-text">Kg</div>
</div>
</div>
</div>
<centile-range :lowerCentile="lowerCentile" :upperCentile="upperCentile"></centile-range>
<div class="form-group form-row">
<label class="col-sm-2 col-form-label" for="dob">Date of Birth</label>
<div class="col-sm-10">
<input class="form-control" type="date" :max="today" v-model="dob" id="dob" />
</div>
<span class="text-danger"></span>
</div>
<fieldset class="form-group">
<div class="form-row">
<legend class="col-form-label col-sm-2 pt-0">Age</legend>
<div class="col-sm-10 age form-inline">
<div class="input-group mb-1">
<input type="number" step="1" min="0" max="130" v-model.number="years" id="age-years" class="form-control" />
<div class="input-group-append">
<div class="input-group-text">years</div>
</div>
</div>
<div class="input-group mb-1">
<input type="number" step="1" min="0" max="37" v-model.number="months" id="age-months" class="form-control" />
<div class="input-group-append">
<div class="input-group-text">months</div>
</div>
</div>
<div class="input-group mb-1">
<input type="number" step="1" min="0" max="90" v-model.number="days" id="age-days" class="form-control" />
<div class="input-group-append">
<div class="input-group-text">days</div>
</div>
</div>
</div>
</div>
</fieldset>
<div class="form-group form-row">
<label class="col-sm-2 col-form-label" for="GestationAtBirth" >Birth Gestation</label>
<div class="input-group col-sm-10">
<input id="GestationAtBirth" type=number min="23" max="43" step="1" class="form-control" v-model="gestation" required/>
<div class="input-group-append">
<div class="input-group-text">weeks</div>
</div>
</div>
<small id="nhiHelp" class="form-text text-muted">for checking weight is correct for age</small>
</div>
</div>
</template>

<script lang="ts">
import Vue from 'vue'
import * as moment from 'moment'
import * as ageHelper from './AgeHelper'
import UKWeightData from '../../CentileData/UkWeightData'
import './centilerange.vue'

const _wtCentiles = new UKWeightData();
export default Vue.extend(
data:function()
return number,
p_months: null as null
,
//components:centilerange,
computed:
'weight':
get: function (this:any)
return this.p_weight;
,
set: function (newVal: any)
this.p_weight = newVal
,
'gestation':
get: function (this:any)
return this.p_gestation;
,
set: function (newVal: number)
this.p_gestation = newVal;
this.setCentiles();

,
'isMale':
get: function (this:any)
return this.p_isMale;
,
set: function (newVal: any)
this.p_isMale = typeof newVal === 'boolean'
?newVal
:null;
this.setCentiles();

,
'days':
get: function (this:any)
return this.p_days;
,
set: function (newVal: any)
,
'months': string)
this.p_months = newVal
,
'years':
get: function (this:any)
return this.p_years;
,
set: function (newVal: number ,
'dob':
get: function (this:any)
return this.p_dob;
,
set: function (newVal: string)
this.p_dob = newVal;
const ageData = ageHelper.daysOfAgeFromDob(newVal);
if (ageData)
this.p_years = ageData.years;
this.p_months = ageData.months;
this.p_days = ageData.days;
this.ageDaysUb = this.ageDaysLb = ageData.totalDays;

this.setCentiles();


,
methods:
setAgeBounds()
let bounds = ageHelper.totalDaysOfAge(this.p_years, this.p_months, this.p_days);
if (bounds === null)
this.ageDaysLb = this.ageDaysUb = null;
else
this.ageDaysLb = bounds.Min;
this.ageDaysUb = bounds.Max;

this.setCentiles();
,
setCentiles() this.ageDaysLb===null)
this.lowerCentile = this.upperCentile = null;
else
this.lowerCentile = 100 * _wtCentiles.cumSnormForAge(this.p_weight, this.ageDaysUb as number, this.p_isMale === false ? false : true, this.p_gestation);
this.upperCentile = this.ageDaysUb === this.ageDaysLb && this.p_isMale !== null
? this.lowerCentile
: 100 * _wtCentiles.cumSnormForAge(this.p_weight, this.ageDaysLb, !!this.p_isMale, this.p_gestation);


,
created: function ()
let self = this;
ageHelper.onNew('day', function (newDate)
self.today = newDate;
)

);
</script>


And the child component which displays the centiles, and if necessary the checkbox to acknowledge any warnings:



<!-- src/components/centilerange.vue -->
<template>
<div class="centile" v-show="lowerVal" :class="alert: true, 'alert-info':!warnCrossed, 'alert-warning':warnCrossed && !limitCrossed, 'alert-danger':limitCrossed ">
<span class="lower">lowerVal<sup>lowerSuffix</sup></span>
<span v-if="upperVal!==lowerVal">
-
<span class="upper">upperVal<sup>upperSuffix</sup></span>
</span>
<span class="centileDescr">
centile
</span>
<div v-if="warnCrossed">
only 1 in denominator
<span v-if="largeNumWord">
largeNumWord
<small>
(10<sup>largeNumExp10</sup>)
</small>
</span>
weigh moreLess.
<div v-if="!limitCrossed" class="form-check form-check-inline">
<input type="checkbox" id="acceptCentile" :checked="acceptWarning" class="form-check-input" required/>
<label for="acceptCentile" class="form-check-label">I confirm this is the correct weight</label>
</div>
</div>
</div>
</template>


<script lang="ts">
import Vue from 'vue'
import largeNumberWords, getSuffix from '../../Utilities/NumberToWords';

const warnCentileUbound = 99;
const warnCentileLbound = 1;
const limitCentileUbound = 100 - 1e-7;
const limitCentileLbound = 1e-12;
export default Vue.component("centile-range",
props:[
'lowerCentile',
'upperCentile'
],
data:function()
return
p_warnCrossed: false,
p_limitCrossed: false,
p_isValid:false,
p_acceptWarning:false,
lowerVal: '',
lowerSuffix: '',
upperVal:'',
upperSuffix:'',
moreLess:'',
denominator:'',
largeNumWord:'',
largeNumExp10:null as null
,
computed:
limitCrossed:
get:function(this:any)
return this.p_limitCrossed;
,
set:function(newVal:boolean)
this.p_limitCrossed = newVal;
this.setValidity();

,
warnCrossed:
get:function(this:any)
return this.p_warnCrossed;
,
set:function(newVal:boolean)
this.p_warnCrossed = newVal;
this.setValidity();

,
acceptWarning:
get:function(this:any)
return this.p_acceptWarning;
,
set:function(newVal:boolean)
this.p_acceptWarning = newVal;
this.setValidity();


,
watch: null),
upperCentile:function(newVal: number ,
methods:
setValidity(),
setWarnings()
const self = this;
if (this.upperCentile === null && this.upperCentile === null)
this.warnCrossed = this.limitCrossed = false;
clearNum();
else minVal > warnCentileUbound;
if (this.limitCrossed

function clearNum()
self.moreLess= self.denominator=self.largeNumWord='';
self.largeNumExp10 = null;



);

function centileText(centile:number)
let l = Math.round(centile);
if (l < 1)
return centile:"<1", suffix: "st"

if (l >= 100)
return centile:">99", suffix: "th"

return centile:l.toString(), suffix: getSuffix(l);

</script>






share|improve this question



























    up vote
    1
    down vote

    favorite












    I am refactoring a very old web page which calculates bolus and infusion drug doses for children from premature through to young adulthood.



    One of the most common drug errors is accidental 10 fold under or overdosing. Given the form needs both weight and age to produce an appropriate chart, it seemed wise to look up statistical data and work out the centile (cumulative standard normal distribution) of weight for age and apply soft and hard limits, with a checkbox saying that values exceeding the soft limits have been double checked. The full code (including the helper functions referenced below) is on GitHub.



    The questions are:



    • Is the extensive use of wrapping data p_varName & computed getters & setters boilerplate for each 'watched' variable the best approach, so that any time age, gender, weight or gestation change, the centile (+/- warnings as required) are updated?

    • Is it the correct thing to have the centile range [bootstrap] alert as a component? This will not be reused, but it felt cleaner to compartmentalise the code/objectives. Is there a better way to achieve this separation?

    • Is emitting the 'validCentile' event when validity changes the best way - or should I use a slot with a 'valid' property on the parent scope (weightage)?

    • All the data properties are 'flat' - i.e. nothing nested - would readability/debugability/general finesse be improved by nesting properties?

    Any thoughts (including those not related to the points above) very much appreciated.



    <!-- src/components/weightage.vue -->
    <template>
    <div class="weightAge">
    <fieldset class="form-group">
    <div class="form-row">
    <legend class="col-form-label col-sm-2 pt-0">Gender</legend>
    <div class="col-sm-10 gender">
    <div class="form-check form-check-inline" id="male">
    <input type="radio" name="gender" id="maleRadio" :value="true" class="form-check-input" v-model="isMale" />
    <label class="form-check-label" for="maleRadio">
    Male
    </label>
    </div>
    <div class="form-check form-check-inline" id="female">
    <input type="radio" name="gender" id="femaleRadio" :value="false" class="form-check-input" v-model="isMale" />
    <label class="form-check-label" for="femaleRadio">
    Female
    </label>
    </div>
    </div>
    </div>
    </fieldset>
    <div class="form-group form-row">
    <label class="col-sm-2 col-form-label" for="Weight" >Weight</label>
    <div class="input-group col-sm-10">
    <input id="Weight" type=number min="0.2" max="400" class="form-control" v-model.number="weight" required />
    <div class="input-group-append">
    <div class="input-group-text">Kg</div>
    </div>
    </div>
    </div>
    <centile-range :lowerCentile="lowerCentile" :upperCentile="upperCentile"></centile-range>
    <div class="form-group form-row">
    <label class="col-sm-2 col-form-label" for="dob">Date of Birth</label>
    <div class="col-sm-10">
    <input class="form-control" type="date" :max="today" v-model="dob" id="dob" />
    </div>
    <span class="text-danger"></span>
    </div>
    <fieldset class="form-group">
    <div class="form-row">
    <legend class="col-form-label col-sm-2 pt-0">Age</legend>
    <div class="col-sm-10 age form-inline">
    <div class="input-group mb-1">
    <input type="number" step="1" min="0" max="130" v-model.number="years" id="age-years" class="form-control" />
    <div class="input-group-append">
    <div class="input-group-text">years</div>
    </div>
    </div>
    <div class="input-group mb-1">
    <input type="number" step="1" min="0" max="37" v-model.number="months" id="age-months" class="form-control" />
    <div class="input-group-append">
    <div class="input-group-text">months</div>
    </div>
    </div>
    <div class="input-group mb-1">
    <input type="number" step="1" min="0" max="90" v-model.number="days" id="age-days" class="form-control" />
    <div class="input-group-append">
    <div class="input-group-text">days</div>
    </div>
    </div>
    </div>
    </div>
    </fieldset>
    <div class="form-group form-row">
    <label class="col-sm-2 col-form-label" for="GestationAtBirth" >Birth Gestation</label>
    <div class="input-group col-sm-10">
    <input id="GestationAtBirth" type=number min="23" max="43" step="1" class="form-control" v-model="gestation" required/>
    <div class="input-group-append">
    <div class="input-group-text">weeks</div>
    </div>
    </div>
    <small id="nhiHelp" class="form-text text-muted">for checking weight is correct for age</small>
    </div>
    </div>
    </template>

    <script lang="ts">
    import Vue from 'vue'
    import * as moment from 'moment'
    import * as ageHelper from './AgeHelper'
    import UKWeightData from '../../CentileData/UkWeightData'
    import './centilerange.vue'

    const _wtCentiles = new UKWeightData();
    export default Vue.extend(
    data:function()
    return number,
    p_months: null as null
    ,
    //components:centilerange,
    computed:
    'weight':
    get: function (this:any)
    return this.p_weight;
    ,
    set: function (newVal: any)
    this.p_weight = newVal
    ,
    'gestation':
    get: function (this:any)
    return this.p_gestation;
    ,
    set: function (newVal: number)
    this.p_gestation = newVal;
    this.setCentiles();

    ,
    'isMale':
    get: function (this:any)
    return this.p_isMale;
    ,
    set: function (newVal: any)
    this.p_isMale = typeof newVal === 'boolean'
    ?newVal
    :null;
    this.setCentiles();

    ,
    'days':
    get: function (this:any)
    return this.p_days;
    ,
    set: function (newVal: any)
    ,
    'months': string)
    this.p_months = newVal
    ,
    'years':
    get: function (this:any)
    return this.p_years;
    ,
    set: function (newVal: number ,
    'dob':
    get: function (this:any)
    return this.p_dob;
    ,
    set: function (newVal: string)
    this.p_dob = newVal;
    const ageData = ageHelper.daysOfAgeFromDob(newVal);
    if (ageData)
    this.p_years = ageData.years;
    this.p_months = ageData.months;
    this.p_days = ageData.days;
    this.ageDaysUb = this.ageDaysLb = ageData.totalDays;

    this.setCentiles();


    ,
    methods:
    setAgeBounds()
    let bounds = ageHelper.totalDaysOfAge(this.p_years, this.p_months, this.p_days);
    if (bounds === null)
    this.ageDaysLb = this.ageDaysUb = null;
    else
    this.ageDaysLb = bounds.Min;
    this.ageDaysUb = bounds.Max;

    this.setCentiles();
    ,
    setCentiles() this.ageDaysLb===null)
    this.lowerCentile = this.upperCentile = null;
    else
    this.lowerCentile = 100 * _wtCentiles.cumSnormForAge(this.p_weight, this.ageDaysUb as number, this.p_isMale === false ? false : true, this.p_gestation);
    this.upperCentile = this.ageDaysUb === this.ageDaysLb && this.p_isMale !== null
    ? this.lowerCentile
    : 100 * _wtCentiles.cumSnormForAge(this.p_weight, this.ageDaysLb, !!this.p_isMale, this.p_gestation);


    ,
    created: function ()
    let self = this;
    ageHelper.onNew('day', function (newDate)
    self.today = newDate;
    )

    );
    </script>


    And the child component which displays the centiles, and if necessary the checkbox to acknowledge any warnings:



    <!-- src/components/centilerange.vue -->
    <template>
    <div class="centile" v-show="lowerVal" :class="alert: true, 'alert-info':!warnCrossed, 'alert-warning':warnCrossed && !limitCrossed, 'alert-danger':limitCrossed ">
    <span class="lower">lowerVal<sup>lowerSuffix</sup></span>
    <span v-if="upperVal!==lowerVal">
    -
    <span class="upper">upperVal<sup>upperSuffix</sup></span>
    </span>
    <span class="centileDescr">
    centile
    </span>
    <div v-if="warnCrossed">
    only 1 in denominator
    <span v-if="largeNumWord">
    largeNumWord
    <small>
    (10<sup>largeNumExp10</sup>)
    </small>
    </span>
    weigh moreLess.
    <div v-if="!limitCrossed" class="form-check form-check-inline">
    <input type="checkbox" id="acceptCentile" :checked="acceptWarning" class="form-check-input" required/>
    <label for="acceptCentile" class="form-check-label">I confirm this is the correct weight</label>
    </div>
    </div>
    </div>
    </template>


    <script lang="ts">
    import Vue from 'vue'
    import largeNumberWords, getSuffix from '../../Utilities/NumberToWords';

    const warnCentileUbound = 99;
    const warnCentileLbound = 1;
    const limitCentileUbound = 100 - 1e-7;
    const limitCentileLbound = 1e-12;
    export default Vue.component("centile-range",
    props:[
    'lowerCentile',
    'upperCentile'
    ],
    data:function()
    return
    p_warnCrossed: false,
    p_limitCrossed: false,
    p_isValid:false,
    p_acceptWarning:false,
    lowerVal: '',
    lowerSuffix: '',
    upperVal:'',
    upperSuffix:'',
    moreLess:'',
    denominator:'',
    largeNumWord:'',
    largeNumExp10:null as null
    ,
    computed:
    limitCrossed:
    get:function(this:any)
    return this.p_limitCrossed;
    ,
    set:function(newVal:boolean)
    this.p_limitCrossed = newVal;
    this.setValidity();

    ,
    warnCrossed:
    get:function(this:any)
    return this.p_warnCrossed;
    ,
    set:function(newVal:boolean)
    this.p_warnCrossed = newVal;
    this.setValidity();

    ,
    acceptWarning:
    get:function(this:any)
    return this.p_acceptWarning;
    ,
    set:function(newVal:boolean)
    this.p_acceptWarning = newVal;
    this.setValidity();


    ,
    watch: null),
    upperCentile:function(newVal: number ,
    methods:
    setValidity(),
    setWarnings()
    const self = this;
    if (this.upperCentile === null && this.upperCentile === null)
    this.warnCrossed = this.limitCrossed = false;
    clearNum();
    else minVal > warnCentileUbound;
    if (this.limitCrossed

    function clearNum()
    self.moreLess= self.denominator=self.largeNumWord='';
    self.largeNumExp10 = null;



    );

    function centileText(centile:number)
    let l = Math.round(centile);
    if (l < 1)
    return centile:"<1", suffix: "st"

    if (l >= 100)
    return centile:">99", suffix: "th"

    return centile:l.toString(), suffix: getSuffix(l);

    </script>






    share|improve this question























      up vote
      1
      down vote

      favorite









      up vote
      1
      down vote

      favorite











      I am refactoring a very old web page which calculates bolus and infusion drug doses for children from premature through to young adulthood.



      One of the most common drug errors is accidental 10 fold under or overdosing. Given the form needs both weight and age to produce an appropriate chart, it seemed wise to look up statistical data and work out the centile (cumulative standard normal distribution) of weight for age and apply soft and hard limits, with a checkbox saying that values exceeding the soft limits have been double checked. The full code (including the helper functions referenced below) is on GitHub.



      The questions are:



      • Is the extensive use of wrapping data p_varName & computed getters & setters boilerplate for each 'watched' variable the best approach, so that any time age, gender, weight or gestation change, the centile (+/- warnings as required) are updated?

      • Is it the correct thing to have the centile range [bootstrap] alert as a component? This will not be reused, but it felt cleaner to compartmentalise the code/objectives. Is there a better way to achieve this separation?

      • Is emitting the 'validCentile' event when validity changes the best way - or should I use a slot with a 'valid' property on the parent scope (weightage)?

      • All the data properties are 'flat' - i.e. nothing nested - would readability/debugability/general finesse be improved by nesting properties?

      Any thoughts (including those not related to the points above) very much appreciated.



      <!-- src/components/weightage.vue -->
      <template>
      <div class="weightAge">
      <fieldset class="form-group">
      <div class="form-row">
      <legend class="col-form-label col-sm-2 pt-0">Gender</legend>
      <div class="col-sm-10 gender">
      <div class="form-check form-check-inline" id="male">
      <input type="radio" name="gender" id="maleRadio" :value="true" class="form-check-input" v-model="isMale" />
      <label class="form-check-label" for="maleRadio">
      Male
      </label>
      </div>
      <div class="form-check form-check-inline" id="female">
      <input type="radio" name="gender" id="femaleRadio" :value="false" class="form-check-input" v-model="isMale" />
      <label class="form-check-label" for="femaleRadio">
      Female
      </label>
      </div>
      </div>
      </div>
      </fieldset>
      <div class="form-group form-row">
      <label class="col-sm-2 col-form-label" for="Weight" >Weight</label>
      <div class="input-group col-sm-10">
      <input id="Weight" type=number min="0.2" max="400" class="form-control" v-model.number="weight" required />
      <div class="input-group-append">
      <div class="input-group-text">Kg</div>
      </div>
      </div>
      </div>
      <centile-range :lowerCentile="lowerCentile" :upperCentile="upperCentile"></centile-range>
      <div class="form-group form-row">
      <label class="col-sm-2 col-form-label" for="dob">Date of Birth</label>
      <div class="col-sm-10">
      <input class="form-control" type="date" :max="today" v-model="dob" id="dob" />
      </div>
      <span class="text-danger"></span>
      </div>
      <fieldset class="form-group">
      <div class="form-row">
      <legend class="col-form-label col-sm-2 pt-0">Age</legend>
      <div class="col-sm-10 age form-inline">
      <div class="input-group mb-1">
      <input type="number" step="1" min="0" max="130" v-model.number="years" id="age-years" class="form-control" />
      <div class="input-group-append">
      <div class="input-group-text">years</div>
      </div>
      </div>
      <div class="input-group mb-1">
      <input type="number" step="1" min="0" max="37" v-model.number="months" id="age-months" class="form-control" />
      <div class="input-group-append">
      <div class="input-group-text">months</div>
      </div>
      </div>
      <div class="input-group mb-1">
      <input type="number" step="1" min="0" max="90" v-model.number="days" id="age-days" class="form-control" />
      <div class="input-group-append">
      <div class="input-group-text">days</div>
      </div>
      </div>
      </div>
      </div>
      </fieldset>
      <div class="form-group form-row">
      <label class="col-sm-2 col-form-label" for="GestationAtBirth" >Birth Gestation</label>
      <div class="input-group col-sm-10">
      <input id="GestationAtBirth" type=number min="23" max="43" step="1" class="form-control" v-model="gestation" required/>
      <div class="input-group-append">
      <div class="input-group-text">weeks</div>
      </div>
      </div>
      <small id="nhiHelp" class="form-text text-muted">for checking weight is correct for age</small>
      </div>
      </div>
      </template>

      <script lang="ts">
      import Vue from 'vue'
      import * as moment from 'moment'
      import * as ageHelper from './AgeHelper'
      import UKWeightData from '../../CentileData/UkWeightData'
      import './centilerange.vue'

      const _wtCentiles = new UKWeightData();
      export default Vue.extend(
      data:function()
      return number,
      p_months: null as null
      ,
      //components:centilerange,
      computed:
      'weight':
      get: function (this:any)
      return this.p_weight;
      ,
      set: function (newVal: any)
      this.p_weight = newVal
      ,
      'gestation':
      get: function (this:any)
      return this.p_gestation;
      ,
      set: function (newVal: number)
      this.p_gestation = newVal;
      this.setCentiles();

      ,
      'isMale':
      get: function (this:any)
      return this.p_isMale;
      ,
      set: function (newVal: any)
      this.p_isMale = typeof newVal === 'boolean'
      ?newVal
      :null;
      this.setCentiles();

      ,
      'days':
      get: function (this:any)
      return this.p_days;
      ,
      set: function (newVal: any)
      ,
      'months': string)
      this.p_months = newVal
      ,
      'years':
      get: function (this:any)
      return this.p_years;
      ,
      set: function (newVal: number ,
      'dob':
      get: function (this:any)
      return this.p_dob;
      ,
      set: function (newVal: string)
      this.p_dob = newVal;
      const ageData = ageHelper.daysOfAgeFromDob(newVal);
      if (ageData)
      this.p_years = ageData.years;
      this.p_months = ageData.months;
      this.p_days = ageData.days;
      this.ageDaysUb = this.ageDaysLb = ageData.totalDays;

      this.setCentiles();


      ,
      methods:
      setAgeBounds()
      let bounds = ageHelper.totalDaysOfAge(this.p_years, this.p_months, this.p_days);
      if (bounds === null)
      this.ageDaysLb = this.ageDaysUb = null;
      else
      this.ageDaysLb = bounds.Min;
      this.ageDaysUb = bounds.Max;

      this.setCentiles();
      ,
      setCentiles() this.ageDaysLb===null)
      this.lowerCentile = this.upperCentile = null;
      else
      this.lowerCentile = 100 * _wtCentiles.cumSnormForAge(this.p_weight, this.ageDaysUb as number, this.p_isMale === false ? false : true, this.p_gestation);
      this.upperCentile = this.ageDaysUb === this.ageDaysLb && this.p_isMale !== null
      ? this.lowerCentile
      : 100 * _wtCentiles.cumSnormForAge(this.p_weight, this.ageDaysLb, !!this.p_isMale, this.p_gestation);


      ,
      created: function ()
      let self = this;
      ageHelper.onNew('day', function (newDate)
      self.today = newDate;
      )

      );
      </script>


      And the child component which displays the centiles, and if necessary the checkbox to acknowledge any warnings:



      <!-- src/components/centilerange.vue -->
      <template>
      <div class="centile" v-show="lowerVal" :class="alert: true, 'alert-info':!warnCrossed, 'alert-warning':warnCrossed && !limitCrossed, 'alert-danger':limitCrossed ">
      <span class="lower">lowerVal<sup>lowerSuffix</sup></span>
      <span v-if="upperVal!==lowerVal">
      -
      <span class="upper">upperVal<sup>upperSuffix</sup></span>
      </span>
      <span class="centileDescr">
      centile
      </span>
      <div v-if="warnCrossed">
      only 1 in denominator
      <span v-if="largeNumWord">
      largeNumWord
      <small>
      (10<sup>largeNumExp10</sup>)
      </small>
      </span>
      weigh moreLess.
      <div v-if="!limitCrossed" class="form-check form-check-inline">
      <input type="checkbox" id="acceptCentile" :checked="acceptWarning" class="form-check-input" required/>
      <label for="acceptCentile" class="form-check-label">I confirm this is the correct weight</label>
      </div>
      </div>
      </div>
      </template>


      <script lang="ts">
      import Vue from 'vue'
      import largeNumberWords, getSuffix from '../../Utilities/NumberToWords';

      const warnCentileUbound = 99;
      const warnCentileLbound = 1;
      const limitCentileUbound = 100 - 1e-7;
      const limitCentileLbound = 1e-12;
      export default Vue.component("centile-range",
      props:[
      'lowerCentile',
      'upperCentile'
      ],
      data:function()
      return
      p_warnCrossed: false,
      p_limitCrossed: false,
      p_isValid:false,
      p_acceptWarning:false,
      lowerVal: '',
      lowerSuffix: '',
      upperVal:'',
      upperSuffix:'',
      moreLess:'',
      denominator:'',
      largeNumWord:'',
      largeNumExp10:null as null
      ,
      computed:
      limitCrossed:
      get:function(this:any)
      return this.p_limitCrossed;
      ,
      set:function(newVal:boolean)
      this.p_limitCrossed = newVal;
      this.setValidity();

      ,
      warnCrossed:
      get:function(this:any)
      return this.p_warnCrossed;
      ,
      set:function(newVal:boolean)
      this.p_warnCrossed = newVal;
      this.setValidity();

      ,
      acceptWarning:
      get:function(this:any)
      return this.p_acceptWarning;
      ,
      set:function(newVal:boolean)
      this.p_acceptWarning = newVal;
      this.setValidity();


      ,
      watch: null),
      upperCentile:function(newVal: number ,
      methods:
      setValidity(),
      setWarnings()
      const self = this;
      if (this.upperCentile === null && this.upperCentile === null)
      this.warnCrossed = this.limitCrossed = false;
      clearNum();
      else minVal > warnCentileUbound;
      if (this.limitCrossed

      function clearNum()
      self.moreLess= self.denominator=self.largeNumWord='';
      self.largeNumExp10 = null;



      );

      function centileText(centile:number)
      let l = Math.round(centile);
      if (l < 1)
      return centile:"<1", suffix: "st"

      if (l >= 100)
      return centile:">99", suffix: "th"

      return centile:l.toString(), suffix: getSuffix(l);

      </script>






      share|improve this question













      I am refactoring a very old web page which calculates bolus and infusion drug doses for children from premature through to young adulthood.



      One of the most common drug errors is accidental 10 fold under or overdosing. Given the form needs both weight and age to produce an appropriate chart, it seemed wise to look up statistical data and work out the centile (cumulative standard normal distribution) of weight for age and apply soft and hard limits, with a checkbox saying that values exceeding the soft limits have been double checked. The full code (including the helper functions referenced below) is on GitHub.



      The questions are:



      • Is the extensive use of wrapping data p_varName & computed getters & setters boilerplate for each 'watched' variable the best approach, so that any time age, gender, weight or gestation change, the centile (+/- warnings as required) are updated?

      • Is it the correct thing to have the centile range [bootstrap] alert as a component? This will not be reused, but it felt cleaner to compartmentalise the code/objectives. Is there a better way to achieve this separation?

      • Is emitting the 'validCentile' event when validity changes the best way - or should I use a slot with a 'valid' property on the parent scope (weightage)?

      • All the data properties are 'flat' - i.e. nothing nested - would readability/debugability/general finesse be improved by nesting properties?

      Any thoughts (including those not related to the points above) very much appreciated.



      <!-- src/components/weightage.vue -->
      <template>
      <div class="weightAge">
      <fieldset class="form-group">
      <div class="form-row">
      <legend class="col-form-label col-sm-2 pt-0">Gender</legend>
      <div class="col-sm-10 gender">
      <div class="form-check form-check-inline" id="male">
      <input type="radio" name="gender" id="maleRadio" :value="true" class="form-check-input" v-model="isMale" />
      <label class="form-check-label" for="maleRadio">
      Male
      </label>
      </div>
      <div class="form-check form-check-inline" id="female">
      <input type="radio" name="gender" id="femaleRadio" :value="false" class="form-check-input" v-model="isMale" />
      <label class="form-check-label" for="femaleRadio">
      Female
      </label>
      </div>
      </div>
      </div>
      </fieldset>
      <div class="form-group form-row">
      <label class="col-sm-2 col-form-label" for="Weight" >Weight</label>
      <div class="input-group col-sm-10">
      <input id="Weight" type=number min="0.2" max="400" class="form-control" v-model.number="weight" required />
      <div class="input-group-append">
      <div class="input-group-text">Kg</div>
      </div>
      </div>
      </div>
      <centile-range :lowerCentile="lowerCentile" :upperCentile="upperCentile"></centile-range>
      <div class="form-group form-row">
      <label class="col-sm-2 col-form-label" for="dob">Date of Birth</label>
      <div class="col-sm-10">
      <input class="form-control" type="date" :max="today" v-model="dob" id="dob" />
      </div>
      <span class="text-danger"></span>
      </div>
      <fieldset class="form-group">
      <div class="form-row">
      <legend class="col-form-label col-sm-2 pt-0">Age</legend>
      <div class="col-sm-10 age form-inline">
      <div class="input-group mb-1">
      <input type="number" step="1" min="0" max="130" v-model.number="years" id="age-years" class="form-control" />
      <div class="input-group-append">
      <div class="input-group-text">years</div>
      </div>
      </div>
      <div class="input-group mb-1">
      <input type="number" step="1" min="0" max="37" v-model.number="months" id="age-months" class="form-control" />
      <div class="input-group-append">
      <div class="input-group-text">months</div>
      </div>
      </div>
      <div class="input-group mb-1">
      <input type="number" step="1" min="0" max="90" v-model.number="days" id="age-days" class="form-control" />
      <div class="input-group-append">
      <div class="input-group-text">days</div>
      </div>
      </div>
      </div>
      </div>
      </fieldset>
      <div class="form-group form-row">
      <label class="col-sm-2 col-form-label" for="GestationAtBirth" >Birth Gestation</label>
      <div class="input-group col-sm-10">
      <input id="GestationAtBirth" type=number min="23" max="43" step="1" class="form-control" v-model="gestation" required/>
      <div class="input-group-append">
      <div class="input-group-text">weeks</div>
      </div>
      </div>
      <small id="nhiHelp" class="form-text text-muted">for checking weight is correct for age</small>
      </div>
      </div>
      </template>

      <script lang="ts">
      import Vue from 'vue'
      import * as moment from 'moment'
      import * as ageHelper from './AgeHelper'
      import UKWeightData from '../../CentileData/UkWeightData'
      import './centilerange.vue'

      const _wtCentiles = new UKWeightData();
      export default Vue.extend(
      data:function()
      return number,
      p_months: null as null
      ,
      //components:centilerange,
      computed:
      'weight':
      get: function (this:any)
      return this.p_weight;
      ,
      set: function (newVal: any)
      this.p_weight = newVal
      ,
      'gestation':
      get: function (this:any)
      return this.p_gestation;
      ,
      set: function (newVal: number)
      this.p_gestation = newVal;
      this.setCentiles();

      ,
      'isMale':
      get: function (this:any)
      return this.p_isMale;
      ,
      set: function (newVal: any)
      this.p_isMale = typeof newVal === 'boolean'
      ?newVal
      :null;
      this.setCentiles();

      ,
      'days':
      get: function (this:any)
      return this.p_days;
      ,
      set: function (newVal: any)
      ,
      'months': string)
      this.p_months = newVal
      ,
      'years':
      get: function (this:any)
      return this.p_years;
      ,
      set: function (newVal: number ,
      'dob':
      get: function (this:any)
      return this.p_dob;
      ,
      set: function (newVal: string)
      this.p_dob = newVal;
      const ageData = ageHelper.daysOfAgeFromDob(newVal);
      if (ageData)
      this.p_years = ageData.years;
      this.p_months = ageData.months;
      this.p_days = ageData.days;
      this.ageDaysUb = this.ageDaysLb = ageData.totalDays;

      this.setCentiles();


      ,
      methods:
      setAgeBounds()
      let bounds = ageHelper.totalDaysOfAge(this.p_years, this.p_months, this.p_days);
      if (bounds === null)
      this.ageDaysLb = this.ageDaysUb = null;
      else
      this.ageDaysLb = bounds.Min;
      this.ageDaysUb = bounds.Max;

      this.setCentiles();
      ,
      setCentiles() this.ageDaysLb===null)
      this.lowerCentile = this.upperCentile = null;
      else
      this.lowerCentile = 100 * _wtCentiles.cumSnormForAge(this.p_weight, this.ageDaysUb as number, this.p_isMale === false ? false : true, this.p_gestation);
      this.upperCentile = this.ageDaysUb === this.ageDaysLb && this.p_isMale !== null
      ? this.lowerCentile
      : 100 * _wtCentiles.cumSnormForAge(this.p_weight, this.ageDaysLb, !!this.p_isMale, this.p_gestation);


      ,
      created: function ()
      let self = this;
      ageHelper.onNew('day', function (newDate)
      self.today = newDate;
      )

      );
      </script>


      And the child component which displays the centiles, and if necessary the checkbox to acknowledge any warnings:



      <!-- src/components/centilerange.vue -->
      <template>
      <div class="centile" v-show="lowerVal" :class="alert: true, 'alert-info':!warnCrossed, 'alert-warning':warnCrossed && !limitCrossed, 'alert-danger':limitCrossed ">
      <span class="lower">lowerVal<sup>lowerSuffix</sup></span>
      <span v-if="upperVal!==lowerVal">
      -
      <span class="upper">upperVal<sup>upperSuffix</sup></span>
      </span>
      <span class="centileDescr">
      centile
      </span>
      <div v-if="warnCrossed">
      only 1 in denominator
      <span v-if="largeNumWord">
      largeNumWord
      <small>
      (10<sup>largeNumExp10</sup>)
      </small>
      </span>
      weigh moreLess.
      <div v-if="!limitCrossed" class="form-check form-check-inline">
      <input type="checkbox" id="acceptCentile" :checked="acceptWarning" class="form-check-input" required/>
      <label for="acceptCentile" class="form-check-label">I confirm this is the correct weight</label>
      </div>
      </div>
      </div>
      </template>


      <script lang="ts">
      import Vue from 'vue'
      import largeNumberWords, getSuffix from '../../Utilities/NumberToWords';

      const warnCentileUbound = 99;
      const warnCentileLbound = 1;
      const limitCentileUbound = 100 - 1e-7;
      const limitCentileLbound = 1e-12;
      export default Vue.component("centile-range",
      props:[
      'lowerCentile',
      'upperCentile'
      ],
      data:function()
      return
      p_warnCrossed: false,
      p_limitCrossed: false,
      p_isValid:false,
      p_acceptWarning:false,
      lowerVal: '',
      lowerSuffix: '',
      upperVal:'',
      upperSuffix:'',
      moreLess:'',
      denominator:'',
      largeNumWord:'',
      largeNumExp10:null as null
      ,
      computed:
      limitCrossed:
      get:function(this:any)
      return this.p_limitCrossed;
      ,
      set:function(newVal:boolean)
      this.p_limitCrossed = newVal;
      this.setValidity();

      ,
      warnCrossed:
      get:function(this:any)
      return this.p_warnCrossed;
      ,
      set:function(newVal:boolean)
      this.p_warnCrossed = newVal;
      this.setValidity();

      ,
      acceptWarning:
      get:function(this:any)
      return this.p_acceptWarning;
      ,
      set:function(newVal:boolean)
      this.p_acceptWarning = newVal;
      this.setValidity();


      ,
      watch: null),
      upperCentile:function(newVal: number ,
      methods:
      setValidity(),
      setWarnings()
      const self = this;
      if (this.upperCentile === null && this.upperCentile === null)
      this.warnCrossed = this.limitCrossed = false;
      clearNum();
      else minVal > warnCentileUbound;
      if (this.limitCrossed

      function clearNum()
      self.moreLess= self.denominator=self.largeNumWord='';
      self.largeNumExp10 = null;



      );

      function centileText(centile:number)
      let l = Math.round(centile);
      if (l < 1)
      return centile:"<1", suffix: "st"

      if (l >= 100)
      return centile:">99", suffix: "th"

      return centile:l.toString(), suffix: getSuffix(l);

      </script>








      share|improve this question












      share|improve this question




      share|improve this question








      edited Mar 3 at 13:41









      Sumurai8

      2,260315




      2,260315









      asked Feb 7 at 5:46









      Brent

      1314




      1314




















          1 Answer
          1






          active

          oldest

          votes

















          up vote
          1
          down vote













          I will start off that I can read typescript to a certain degree, but I have never used typescript with Vue before, so I can't really comment on your use of typescript here.



          Side-effects in computed properties



          All your computed properties have side-effects in their set-function. This side-effect seems to result in calculating the value of two different variables. Try to avoid this: Keep your computed properties simple and only calculate that value in that computation function. If another variable depends on your computed property, just put that one in a computed property as well. Vue will handle the dependencies and recalculate it.



          If you need to have some kind of side-effect, for example to invoke a debounced calculation for something that takes a significant amount of time or to do an api call, create a watcher instead:



          import myApiCall from 'api/myApi';

          // ES5 syntax
          export default
          computed:
          somethingImportant()
          return Math.floor(Math.random() * 10);

          ,

          watch:
          somethingImportant(newValue)
          myApiCall(newValue).then((result) =>
          console.log(result);
          );





          By doing it this way, you make the code easier to read. The computed property now just calculates something from other data. The watcher now takes care of all the side-effects.



          Your questions




          Is the extensive use of wrapping data p_varName & computed getters &
          setters boilerplate for each 'watched' variable the best approach, so
          that any time age, gender, weight or gestation change, the centile
          (+/- warnings as required) are updated?




          See above. The better approach is to use computed properties and watchers.




          Is it the correct thing to have the centile range [bootstrap] alert as
          a component? This will not be reused, but it felt cleaner to
          compartmentalise the code/objectives. Is there a better way to achieve
          this separation?




          When you can extract something that feels "complete" out of a component and do not have to pass half of the internal state to that component as well, extracting such a part to a separate component could be a good choice. If you need to pass a significant amount of the internal state to the new component, it means that the component is not as separate as you once thought.



          Extracting a component allows you to extract away its internal state away. If done correctly, it means that you now have two simpler components, which are easier to understand and reason about. I think your extraction of the centilerange component is a good choice.




          Is emitting the 'validCentile' event when validity changes the best
          way - or should I use a slot with a 'valid' property on the parent
          scope (weightage)?




          If you need to pass data from a child to a direct parent, use events. If you need to pass data from a component to an ancestor, or to a sibling, consider using either an event bus (e.g. a dummy component bound to this.$bus), or a vuex store mutation.



          If you use the value property and the input event, you can use the v-model syntactic sugar in the parent component. If you only use the event to communicate a value back, this is probably the best way.




          All the data properties are 'flat' - i.e. nothing nested - would
          readability/debugability/general finesse be improved by nesting
          properties?




          This is mostly personal preference. I would recommend grouping similar properties under a single object, to avoid having to have an ever increasing list of properties that you need to pass to a child object. A perfect example might be a configuration object. Keep in mind that you need to document somehow what keys are required and what keys are optional in that object, and what they roughly mean. Since you are passing an object around, this information is not as immediately clear as passing several separate properties around.



          Some other feedback



          You are using the same overall order of component "parts" (data, methods, lifecycle methods), which is a good thing.



          Consider factoring out the inline object for your class definition, and move it to a computed property instead. This will make it easier to add or remove classes, and make it clearer when you make dynamic class names, and make your html lines shorter. Consider moving attributes to a new line when your lines get too long.



          Consider using ES6 syntax more. I see you are using let/const and destructuring, but there are much more nice things in ES6. Things like method shorthands and the spread operator come to mind.



          Consider factoring out any function that is not in the component export to a separate file. If you don't believe it belongs in the internal structure of a component, it probably does not belong in the single component file.



          Your second component file needs some cleaning up. I don't think it is necessary to define a function inside setWarnings. Just define it in the methods object and call it with this.clearNum(). There are some operators that have inconsistent whitespace around them.






          share|improve this answer























            Your Answer




            StackExchange.ifUsing("editor", function ()
            return StackExchange.using("mathjaxEditing", function ()
            StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
            StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
            );
            );
            , "mathjax-editing");

            StackExchange.ifUsing("editor", function ()
            StackExchange.using("externalEditor", function ()
            StackExchange.using("snippets", function ()
            StackExchange.snippets.init();
            );
            );
            , "code-snippets");

            StackExchange.ready(function()
            var channelOptions =
            tags: "".split(" "),
            id: "196"
            ;
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function()
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled)
            StackExchange.using("snippets", function()
            createEditor();
            );

            else
            createEditor();

            );

            function createEditor()
            StackExchange.prepareEditor(
            heartbeatType: 'answer',
            convertImagesToLinks: false,
            noModals: false,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: null,
            bindNavPrevention: true,
            postfix: "",
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            );



            );








             

            draft saved


            draft discarded


















            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f186968%2fusing-vue-to-help-form-validation-with-weight-for-age%23new-answer', 'question_page');

            );

            Post as a guest






























            1 Answer
            1






            active

            oldest

            votes








            1 Answer
            1






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes








            up vote
            1
            down vote













            I will start off that I can read typescript to a certain degree, but I have never used typescript with Vue before, so I can't really comment on your use of typescript here.



            Side-effects in computed properties



            All your computed properties have side-effects in their set-function. This side-effect seems to result in calculating the value of two different variables. Try to avoid this: Keep your computed properties simple and only calculate that value in that computation function. If another variable depends on your computed property, just put that one in a computed property as well. Vue will handle the dependencies and recalculate it.



            If you need to have some kind of side-effect, for example to invoke a debounced calculation for something that takes a significant amount of time or to do an api call, create a watcher instead:



            import myApiCall from 'api/myApi';

            // ES5 syntax
            export default
            computed:
            somethingImportant()
            return Math.floor(Math.random() * 10);

            ,

            watch:
            somethingImportant(newValue)
            myApiCall(newValue).then((result) =>
            console.log(result);
            );





            By doing it this way, you make the code easier to read. The computed property now just calculates something from other data. The watcher now takes care of all the side-effects.



            Your questions




            Is the extensive use of wrapping data p_varName & computed getters &
            setters boilerplate for each 'watched' variable the best approach, so
            that any time age, gender, weight or gestation change, the centile
            (+/- warnings as required) are updated?




            See above. The better approach is to use computed properties and watchers.




            Is it the correct thing to have the centile range [bootstrap] alert as
            a component? This will not be reused, but it felt cleaner to
            compartmentalise the code/objectives. Is there a better way to achieve
            this separation?




            When you can extract something that feels "complete" out of a component and do not have to pass half of the internal state to that component as well, extracting such a part to a separate component could be a good choice. If you need to pass a significant amount of the internal state to the new component, it means that the component is not as separate as you once thought.



            Extracting a component allows you to extract away its internal state away. If done correctly, it means that you now have two simpler components, which are easier to understand and reason about. I think your extraction of the centilerange component is a good choice.




            Is emitting the 'validCentile' event when validity changes the best
            way - or should I use a slot with a 'valid' property on the parent
            scope (weightage)?




            If you need to pass data from a child to a direct parent, use events. If you need to pass data from a component to an ancestor, or to a sibling, consider using either an event bus (e.g. a dummy component bound to this.$bus), or a vuex store mutation.



            If you use the value property and the input event, you can use the v-model syntactic sugar in the parent component. If you only use the event to communicate a value back, this is probably the best way.




            All the data properties are 'flat' - i.e. nothing nested - would
            readability/debugability/general finesse be improved by nesting
            properties?




            This is mostly personal preference. I would recommend grouping similar properties under a single object, to avoid having to have an ever increasing list of properties that you need to pass to a child object. A perfect example might be a configuration object. Keep in mind that you need to document somehow what keys are required and what keys are optional in that object, and what they roughly mean. Since you are passing an object around, this information is not as immediately clear as passing several separate properties around.



            Some other feedback



            You are using the same overall order of component "parts" (data, methods, lifecycle methods), which is a good thing.



            Consider factoring out the inline object for your class definition, and move it to a computed property instead. This will make it easier to add or remove classes, and make it clearer when you make dynamic class names, and make your html lines shorter. Consider moving attributes to a new line when your lines get too long.



            Consider using ES6 syntax more. I see you are using let/const and destructuring, but there are much more nice things in ES6. Things like method shorthands and the spread operator come to mind.



            Consider factoring out any function that is not in the component export to a separate file. If you don't believe it belongs in the internal structure of a component, it probably does not belong in the single component file.



            Your second component file needs some cleaning up. I don't think it is necessary to define a function inside setWarnings. Just define it in the methods object and call it with this.clearNum(). There are some operators that have inconsistent whitespace around them.






            share|improve this answer



























              up vote
              1
              down vote













              I will start off that I can read typescript to a certain degree, but I have never used typescript with Vue before, so I can't really comment on your use of typescript here.



              Side-effects in computed properties



              All your computed properties have side-effects in their set-function. This side-effect seems to result in calculating the value of two different variables. Try to avoid this: Keep your computed properties simple and only calculate that value in that computation function. If another variable depends on your computed property, just put that one in a computed property as well. Vue will handle the dependencies and recalculate it.



              If you need to have some kind of side-effect, for example to invoke a debounced calculation for something that takes a significant amount of time or to do an api call, create a watcher instead:



              import myApiCall from 'api/myApi';

              // ES5 syntax
              export default
              computed:
              somethingImportant()
              return Math.floor(Math.random() * 10);

              ,

              watch:
              somethingImportant(newValue)
              myApiCall(newValue).then((result) =>
              console.log(result);
              );





              By doing it this way, you make the code easier to read. The computed property now just calculates something from other data. The watcher now takes care of all the side-effects.



              Your questions




              Is the extensive use of wrapping data p_varName & computed getters &
              setters boilerplate for each 'watched' variable the best approach, so
              that any time age, gender, weight or gestation change, the centile
              (+/- warnings as required) are updated?




              See above. The better approach is to use computed properties and watchers.




              Is it the correct thing to have the centile range [bootstrap] alert as
              a component? This will not be reused, but it felt cleaner to
              compartmentalise the code/objectives. Is there a better way to achieve
              this separation?




              When you can extract something that feels "complete" out of a component and do not have to pass half of the internal state to that component as well, extracting such a part to a separate component could be a good choice. If you need to pass a significant amount of the internal state to the new component, it means that the component is not as separate as you once thought.



              Extracting a component allows you to extract away its internal state away. If done correctly, it means that you now have two simpler components, which are easier to understand and reason about. I think your extraction of the centilerange component is a good choice.




              Is emitting the 'validCentile' event when validity changes the best
              way - or should I use a slot with a 'valid' property on the parent
              scope (weightage)?




              If you need to pass data from a child to a direct parent, use events. If you need to pass data from a component to an ancestor, or to a sibling, consider using either an event bus (e.g. a dummy component bound to this.$bus), or a vuex store mutation.



              If you use the value property and the input event, you can use the v-model syntactic sugar in the parent component. If you only use the event to communicate a value back, this is probably the best way.




              All the data properties are 'flat' - i.e. nothing nested - would
              readability/debugability/general finesse be improved by nesting
              properties?




              This is mostly personal preference. I would recommend grouping similar properties under a single object, to avoid having to have an ever increasing list of properties that you need to pass to a child object. A perfect example might be a configuration object. Keep in mind that you need to document somehow what keys are required and what keys are optional in that object, and what they roughly mean. Since you are passing an object around, this information is not as immediately clear as passing several separate properties around.



              Some other feedback



              You are using the same overall order of component "parts" (data, methods, lifecycle methods), which is a good thing.



              Consider factoring out the inline object for your class definition, and move it to a computed property instead. This will make it easier to add or remove classes, and make it clearer when you make dynamic class names, and make your html lines shorter. Consider moving attributes to a new line when your lines get too long.



              Consider using ES6 syntax more. I see you are using let/const and destructuring, but there are much more nice things in ES6. Things like method shorthands and the spread operator come to mind.



              Consider factoring out any function that is not in the component export to a separate file. If you don't believe it belongs in the internal structure of a component, it probably does not belong in the single component file.



              Your second component file needs some cleaning up. I don't think it is necessary to define a function inside setWarnings. Just define it in the methods object and call it with this.clearNum(). There are some operators that have inconsistent whitespace around them.






              share|improve this answer

























                up vote
                1
                down vote










                up vote
                1
                down vote









                I will start off that I can read typescript to a certain degree, but I have never used typescript with Vue before, so I can't really comment on your use of typescript here.



                Side-effects in computed properties



                All your computed properties have side-effects in their set-function. This side-effect seems to result in calculating the value of two different variables. Try to avoid this: Keep your computed properties simple and only calculate that value in that computation function. If another variable depends on your computed property, just put that one in a computed property as well. Vue will handle the dependencies and recalculate it.



                If you need to have some kind of side-effect, for example to invoke a debounced calculation for something that takes a significant amount of time or to do an api call, create a watcher instead:



                import myApiCall from 'api/myApi';

                // ES5 syntax
                export default
                computed:
                somethingImportant()
                return Math.floor(Math.random() * 10);

                ,

                watch:
                somethingImportant(newValue)
                myApiCall(newValue).then((result) =>
                console.log(result);
                );





                By doing it this way, you make the code easier to read. The computed property now just calculates something from other data. The watcher now takes care of all the side-effects.



                Your questions




                Is the extensive use of wrapping data p_varName & computed getters &
                setters boilerplate for each 'watched' variable the best approach, so
                that any time age, gender, weight or gestation change, the centile
                (+/- warnings as required) are updated?




                See above. The better approach is to use computed properties and watchers.




                Is it the correct thing to have the centile range [bootstrap] alert as
                a component? This will not be reused, but it felt cleaner to
                compartmentalise the code/objectives. Is there a better way to achieve
                this separation?




                When you can extract something that feels "complete" out of a component and do not have to pass half of the internal state to that component as well, extracting such a part to a separate component could be a good choice. If you need to pass a significant amount of the internal state to the new component, it means that the component is not as separate as you once thought.



                Extracting a component allows you to extract away its internal state away. If done correctly, it means that you now have two simpler components, which are easier to understand and reason about. I think your extraction of the centilerange component is a good choice.




                Is emitting the 'validCentile' event when validity changes the best
                way - or should I use a slot with a 'valid' property on the parent
                scope (weightage)?




                If you need to pass data from a child to a direct parent, use events. If you need to pass data from a component to an ancestor, or to a sibling, consider using either an event bus (e.g. a dummy component bound to this.$bus), or a vuex store mutation.



                If you use the value property and the input event, you can use the v-model syntactic sugar in the parent component. If you only use the event to communicate a value back, this is probably the best way.




                All the data properties are 'flat' - i.e. nothing nested - would
                readability/debugability/general finesse be improved by nesting
                properties?




                This is mostly personal preference. I would recommend grouping similar properties under a single object, to avoid having to have an ever increasing list of properties that you need to pass to a child object. A perfect example might be a configuration object. Keep in mind that you need to document somehow what keys are required and what keys are optional in that object, and what they roughly mean. Since you are passing an object around, this information is not as immediately clear as passing several separate properties around.



                Some other feedback



                You are using the same overall order of component "parts" (data, methods, lifecycle methods), which is a good thing.



                Consider factoring out the inline object for your class definition, and move it to a computed property instead. This will make it easier to add or remove classes, and make it clearer when you make dynamic class names, and make your html lines shorter. Consider moving attributes to a new line when your lines get too long.



                Consider using ES6 syntax more. I see you are using let/const and destructuring, but there are much more nice things in ES6. Things like method shorthands and the spread operator come to mind.



                Consider factoring out any function that is not in the component export to a separate file. If you don't believe it belongs in the internal structure of a component, it probably does not belong in the single component file.



                Your second component file needs some cleaning up. I don't think it is necessary to define a function inside setWarnings. Just define it in the methods object and call it with this.clearNum(). There are some operators that have inconsistent whitespace around them.






                share|improve this answer















                I will start off that I can read typescript to a certain degree, but I have never used typescript with Vue before, so I can't really comment on your use of typescript here.



                Side-effects in computed properties



                All your computed properties have side-effects in their set-function. This side-effect seems to result in calculating the value of two different variables. Try to avoid this: Keep your computed properties simple and only calculate that value in that computation function. If another variable depends on your computed property, just put that one in a computed property as well. Vue will handle the dependencies and recalculate it.



                If you need to have some kind of side-effect, for example to invoke a debounced calculation for something that takes a significant amount of time or to do an api call, create a watcher instead:



                import myApiCall from 'api/myApi';

                // ES5 syntax
                export default
                computed:
                somethingImportant()
                return Math.floor(Math.random() * 10);

                ,

                watch:
                somethingImportant(newValue)
                myApiCall(newValue).then((result) =>
                console.log(result);
                );





                By doing it this way, you make the code easier to read. The computed property now just calculates something from other data. The watcher now takes care of all the side-effects.



                Your questions




                Is the extensive use of wrapping data p_varName & computed getters &
                setters boilerplate for each 'watched' variable the best approach, so
                that any time age, gender, weight or gestation change, the centile
                (+/- warnings as required) are updated?




                See above. The better approach is to use computed properties and watchers.




                Is it the correct thing to have the centile range [bootstrap] alert as
                a component? This will not be reused, but it felt cleaner to
                compartmentalise the code/objectives. Is there a better way to achieve
                this separation?




                When you can extract something that feels "complete" out of a component and do not have to pass half of the internal state to that component as well, extracting such a part to a separate component could be a good choice. If you need to pass a significant amount of the internal state to the new component, it means that the component is not as separate as you once thought.



                Extracting a component allows you to extract away its internal state away. If done correctly, it means that you now have two simpler components, which are easier to understand and reason about. I think your extraction of the centilerange component is a good choice.




                Is emitting the 'validCentile' event when validity changes the best
                way - or should I use a slot with a 'valid' property on the parent
                scope (weightage)?




                If you need to pass data from a child to a direct parent, use events. If you need to pass data from a component to an ancestor, or to a sibling, consider using either an event bus (e.g. a dummy component bound to this.$bus), or a vuex store mutation.



                If you use the value property and the input event, you can use the v-model syntactic sugar in the parent component. If you only use the event to communicate a value back, this is probably the best way.




                All the data properties are 'flat' - i.e. nothing nested - would
                readability/debugability/general finesse be improved by nesting
                properties?




                This is mostly personal preference. I would recommend grouping similar properties under a single object, to avoid having to have an ever increasing list of properties that you need to pass to a child object. A perfect example might be a configuration object. Keep in mind that you need to document somehow what keys are required and what keys are optional in that object, and what they roughly mean. Since you are passing an object around, this information is not as immediately clear as passing several separate properties around.



                Some other feedback



                You are using the same overall order of component "parts" (data, methods, lifecycle methods), which is a good thing.



                Consider factoring out the inline object for your class definition, and move it to a computed property instead. This will make it easier to add or remove classes, and make it clearer when you make dynamic class names, and make your html lines shorter. Consider moving attributes to a new line when your lines get too long.



                Consider using ES6 syntax more. I see you are using let/const and destructuring, but there are much more nice things in ES6. Things like method shorthands and the spread operator come to mind.



                Consider factoring out any function that is not in the component export to a separate file. If you don't believe it belongs in the internal structure of a component, it probably does not belong in the single component file.



                Your second component file needs some cleaning up. I don't think it is necessary to define a function inside setWarnings. Just define it in the methods object and call it with this.clearNum(). There are some operators that have inconsistent whitespace around them.







                share|improve this answer















                share|improve this answer



                share|improve this answer








                edited Mar 26 at 16:37


























                answered Mar 6 at 22:03









                Sumurai8

                2,260315




                2,260315






















                     

                    draft saved


                    draft discarded


























                     


                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function ()
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f186968%2fusing-vue-to-help-form-validation-with-weight-for-age%23new-answer', 'question_page');

                    );

                    Post as a guest













































































                    Popular posts from this blog

                    Chat program with C++ and SFML

                    Function to Return a JSON Like Objects Using VBA Collections and Arrays

                    Will my employers contract hold up in court?