Community Tip - Did you get called away in the middle of writing a post? Don't worry you can find your unfinished post later in the Drafts section of your profile page. X
Related to this idea request: https://community.ptc.com/t5/Windchill-Ideas/Support-conversion-of-fractions-to-decimals-for-IBA-real-number/idi-p/1039253
I started poking in to see how this could be done. I noticed that attributes with real numbers with units have a JS validation script associated onBlur. The function is "jsca.columns.numericRenderer.validateFloatingPointWithUnit" but surprisingly, this function is defined in 5 different locations in the codebase. Not sure which one is the one called when the attribute entry is validated but it should not be too hard to narrow that down.
Digging further, this function simply takes in a regular expression and ensures that the entry matches it:
Pumping this into AI let's us know what its checking:
The regular expression [^0-9\-\,\.E] matches any single character that is not a digit, a hyphen, a comma, a period, or the uppercase letter E.
Here is a breakdown of the expression:
[]: This is a character class, which specifies a set of characters to match. It matches any one character contained within the brackets.
^: Placed at the very beginning of a character class, this symbol negates the set. It means to match any character not in the specified list.
0-9: This is a range that matches any single digit from 0 to 9.
\-: A hyphen (-) inside a character class can indicate a range, but since it appears before or after other characters, it loses its special meaning and is treated as a literal hyphen. However, escaping it with a backslash (\) explicitly ensures it is treated as a literal character.
\,: The comma is a literal character. Escaping it with a backslash is unnecessary but harmless. It matches a single comma.
\.: A period (.) normally matches any character, but inside a character class, it is treated as a literal period. The backslash escapes it, but it still means "a literal period".
E: The uppercase letter E is a literal character and matches only the letter E.
Actually, the function looks at the value before the first space character if it exists and ensures that the first part matches a valid number. Simple.
My thought would be to hook in another JS call before all this to transform the input if it encounted a "/" character in that first part, converting it to a decimal. What do you think? I can call it only if it detects a "/" otherwise pass through. Still strange there is 5 copies of this function floating around...
Solved! Go to Solution.
Ok, I ended up taking this path. I modified the numericJSRenderer.jsfrag file instead of putting my stuff in a custom.jsfrag files. Why? Well, I had to make changes to jsca.columns.numericRenderer.validateFloatingPointWithUnit() to have it call my method first so figured it was one less file to manage. So, basically if we detect a single /, send it to my fuction to convert it back to decimal. If it ends up detecting bad input, it returns null, and normal error message. If not, modify the value and also update the input field to show decimals. Seems to work great up need to test a bit more. Here is code. If you spot a bug or edge case, let me know.
/**
*Validates the given numeric input element value to check if its number or its number plus unit.
*@private
*/
jsca.columns.numericRenderer.validateFloatingPointWithUnit = function(elem, errorMsgRB, regEx){
var value = elem.value;
value = value.trim();
//test if fraction entered
var fracArr = value.split("/");
if (fracArr.length === 2) {
//user entered a normal fractional value
var transformedValue=jsca.columns.numericRenderer.convertFractionToFloatingPoint(value);
if (!transformedValue) {
JCAAlert(errorMsgRB);
} else {
value=transformedValue;
elem.value= transformedValue;
}
} else if (fracArr.length > 2) {
//user entered 1/3/2 m which is invalid
JCAAlert(errorMsgRB);
}
var strArr = value.split(" ");
if (strArr.length === 1) {
//user entered just the number, no unit.
PTC.wizard.validateInputValue(elem, value, regEx, errorMsgRB, []);
} else if (strArr.length === 2){
//user entered 10.32 m, validates just the first part. Units is validated by type layer on form submission.
PTC.wizard.validateInputValue(elem, strArr[0], regEx, "com.ptc.core.ui.errorMessagesRB.NUMERIC_VALUE_ERROR_MSG", []);
} else if (strArr.length > 2){
//user entered 10.32 m mm which is invalid
JCAAlert(errorMsgRB);
}
};
/**
*Takes a whole, faction or mixed fraction and converts to floating point decimal value
*Called from within jsca.columns.numericRenderer.validateFloatingPointWithUnit as a hook
*Assumed value must contain a / which was validated in calling function
*@private
*/
jsca.columns.numericRenderer.convertFractionToFloatingPoint = function(value){
const isNegative = value.startsWith('-');
// Remove the negative sign and any trailing non-numeric units
const cleanInput = (isNegative ? value.substring(1) : value).split(/\s+/);
let wholeNumber = 0;
let fractionPart = "0/1";
let unit = "";
// 2. Parse numeric parts (Whole number, Fraction, or Decimal)
if (cleanInput.length === 3) {
// Mixed fraction with unit: ["1", "1/2", "m"]
wholeNumber = parseFloat(cleanInput[0]);
fractionPart = cleanInput[1];
unit = cleanInput[2];
} else if (cleanInput.length === 2) { //fraction with unit or mixed case without
// Simple fraction with unit: ["3/4","m"] or mixed fraction: ["1","3/4"]
if (cleanInput[0].includes('/')) {
fractionPart = cleanInput[0];
unit=cleanInput[1];
} else { //mixed fraction with no unit
wholeNumber = parseFloat(cleanInput[0]);
fractionPart = cleanInput[1];
}
} else if (cleanInput.length === 1) {
// Simple fraction: ["3/4"]
fractionPart = cleanInput[0];
} else { //not a case we are handling
return null;
}
// 3. Process the fraction part
const [num, den] = fractionPart.split('/').map(Number);
if (isNaN(num) || isNaN(den) || den === 0) return null;
// 4. Combine magnitude and re-apply sign
const magnitude = wholeNumber + (num / den);
const finalValue = isNegative ? -magnitude : magnitude;
// 5. Round to max 6 decimal places
// toFixed(6) handles repeating decimals by rounding [MDN Web Docs]
return parseFloat(finalValue.toFixed(6))+" " + unit;
};
Here is an animation of it working.
https://www.ptc.com/en/support/article/CS71276?source=search
Article above provides a method for customization. Using this, netmarkets\javascript\util\jsfrags\attribute\numericJSRenderer.jsfrag is the customization point but you also need to compile this according to the link above to update windchill-all.js which is what is referenced in the page. Easy enough for AI to handle coding but here are the cases we need to consider:
We should be able to insert a method call to this function which converts it, update the element and continues on to validation. I will post when complete for all.
Ok, I ended up taking this path. I modified the numericJSRenderer.jsfrag file instead of putting my stuff in a custom.jsfrag files. Why? Well, I had to make changes to jsca.columns.numericRenderer.validateFloatingPointWithUnit() to have it call my method first so figured it was one less file to manage. So, basically if we detect a single /, send it to my fuction to convert it back to decimal. If it ends up detecting bad input, it returns null, and normal error message. If not, modify the value and also update the input field to show decimals. Seems to work great up need to test a bit more. Here is code. If you spot a bug or edge case, let me know.
/**
*Validates the given numeric input element value to check if its number or its number plus unit.
*@private
*/
jsca.columns.numericRenderer.validateFloatingPointWithUnit = function(elem, errorMsgRB, regEx){
var value = elem.value;
value = value.trim();
//test if fraction entered
var fracArr = value.split("/");
if (fracArr.length === 2) {
//user entered a normal fractional value
var transformedValue=jsca.columns.numericRenderer.convertFractionToFloatingPoint(value);
if (!transformedValue) {
JCAAlert(errorMsgRB);
} else {
value=transformedValue;
elem.value= transformedValue;
}
} else if (fracArr.length > 2) {
//user entered 1/3/2 m which is invalid
JCAAlert(errorMsgRB);
}
var strArr = value.split(" ");
if (strArr.length === 1) {
//user entered just the number, no unit.
PTC.wizard.validateInputValue(elem, value, regEx, errorMsgRB, []);
} else if (strArr.length === 2){
//user entered 10.32 m, validates just the first part. Units is validated by type layer on form submission.
PTC.wizard.validateInputValue(elem, strArr[0], regEx, "com.ptc.core.ui.errorMessagesRB.NUMERIC_VALUE_ERROR_MSG", []);
} else if (strArr.length > 2){
//user entered 10.32 m mm which is invalid
JCAAlert(errorMsgRB);
}
};
/**
*Takes a whole, faction or mixed fraction and converts to floating point decimal value
*Called from within jsca.columns.numericRenderer.validateFloatingPointWithUnit as a hook
*Assumed value must contain a / which was validated in calling function
*@private
*/
jsca.columns.numericRenderer.convertFractionToFloatingPoint = function(value){
const isNegative = value.startsWith('-');
// Remove the negative sign and any trailing non-numeric units
const cleanInput = (isNegative ? value.substring(1) : value).split(/\s+/);
let wholeNumber = 0;
let fractionPart = "0/1";
let unit = "";
// 2. Parse numeric parts (Whole number, Fraction, or Decimal)
if (cleanInput.length === 3) {
// Mixed fraction with unit: ["1", "1/2", "m"]
wholeNumber = parseFloat(cleanInput[0]);
fractionPart = cleanInput[1];
unit = cleanInput[2];
} else if (cleanInput.length === 2) { //fraction with unit or mixed case without
// Simple fraction with unit: ["3/4","m"] or mixed fraction: ["1","3/4"]
if (cleanInput[0].includes('/')) {
fractionPart = cleanInput[0];
unit=cleanInput[1];
} else { //mixed fraction with no unit
wholeNumber = parseFloat(cleanInput[0]);
fractionPart = cleanInput[1];
}
} else if (cleanInput.length === 1) {
// Simple fraction: ["3/4"]
fractionPart = cleanInput[0];
} else { //not a case we are handling
return null;
}
// 3. Process the fraction part
const [num, den] = fractionPart.split('/').map(Number);
if (isNaN(num) || isNaN(den) || den === 0) return null;
// 4. Combine magnitude and re-apply sign
const magnitude = wholeNumber + (num / den);
const finalValue = isNegative ? -magnitude : magnitude;
// 5. Round to max 6 decimal places
// toFixed(6) handles repeating decimals by rounding [MDN Web Docs]
return parseFloat(finalValue.toFixed(6))+" " + unit;
};
Here is an animation of it working.
I did not write support for #4 but validated with these test cases:
console.log(convertFractionToFloatingPoint("-1/2"));
console.log(convertFractionToFloatingPoint("9/0"));
console.log(convertFractionToFloatingPoint("1 1/4"));
console.log(convertFractionToFloatingPoint("1 1/0"));
console.log(convertFractionToFloatingPoint("6/3"));
console.log(convertFractionToFloatingPoint("1/3"));
console.log(convertFractionToFloatingPoint("2/7"));
console.log(convertFractionToFloatingPoint("1/64"));
console.log(convertFractionToFloatingPoint("1/8 in"));
console.log(convertFractionToFloatingPoint("1 3/8 k"));
console.log(convertFractionToFloatingPoint("-1/6"));
console.log(convertFractionToFloatingPoint("-1.3/6"));
console.log(convertFractionToFloatingPoint("-2 1/6"));
console.log(convertFractionToFloatingPoint("-2 1/4.2"));
console.log(convertFractionToFloatingPoint("-2 1/6 m"));
console.log(convertFractionToFloatingPoint("IN/Valid m"));
console.log(convertFractionToFloatingPoint("IN/Valid"));
-0.5
null
1.25
null
2
0.333333
0.285714
0.015625
0.125 in
1.375 k
-0.166667
-0.216667
-2.166667
-2.238095
-2.166667 m
null
null
null
