Skip to content

Instantly share code, notes, and snippets.

@dbcook
Created June 10, 2019 22:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dbcook/598e0a0c00df1c83ab728fba10f21b2a to your computer and use it in GitHub Desktop.
Save dbcook/598e0a0c00df1c83ab728fba10f21b2a to your computer and use it in GitHub Desktop.
embeddable scale modeling dimension calculator
/**
* <h1>Scale model calculator gadget</h1>
* This is a well-featured page-embeddable gadget to compute dimensions
* for scale models given the real-world prototype dimensions.
*
* <h2>Features</h2>
* <ul>
* <li>Selectable categories of predefined scales, e.g. model RR, plastic model, etc.
* <li>Supports arbitrary user-defined scale ratios.
* <li>Selectable sets of units for prototype and scale dimensions.
* <li>Reverse lookup of predefined scales when user types a user-defined scale factor.
* <li>Writes output history with computed results.
* <li>Selectable output history formats
* <ul>
* <li>Text with formatted tabular scale factors</li>
* <li>CSV for spreadsheet import</li>
* </ul>
* <li>Cookie persistence of preferences and recent output history.
* <li>Internationalization support.
* <li>Full CSS style and layout control.
* <li>Designed with HTML/CSS layout and Javascript UI widgets that bind to the HTML.
* <li>JavaScript code Passes jslint.
* <li>Full JavaScript code documentation with YUI Doc tags.
* </ul>
*
* <h2>Todo list</h2>
* <ul>
* <li>Button to emit a CSV header line to the output (pretty easy).</li>
* <li>Button to copy the calc history to the clipboard. Easy on IE, harder on FFox
* due to higher security levels.
* <li>Upload a file of dimensions and process it (requires server-side code)
* <li>Link to get calc tape output as XML file (requires server-side code)
* </ul>
*
* <h2>Configuration and Installation</h2>
* <h3>Required Tools</h3>
* For various reasons it's easier to install and run all the JavaScript tools on Linux. It can be done on Windows
* but that requires installing additional layers of tools just to have a Make utility and Bash shell.
*
* <p>To build the minified JavaScript, verify jslint compliance, and generate the code documentation, you need
* <ul>
* <li>Gnu Make. On Linux this is almost always present. On Windows you can use a Cygwin shell with Make installed.</li>
* <li><a href="http://developer.yahoo.com/yui/compressor/">YUI Compressor</a>. The fully-commented scalecalc package
* will shrink dramatically when minified, so it's highly recommended that you do so. You could also use
* the Google Closure compiler which is even more aggressive.</li>
* <li><a href="http://developer.yahoo.com/yui/yuidoc/">YUI Doc</a>. YUI Doc is much more robust than 'jsdoc'.
* The latter blows up on large source files and doesn't handle all forms of class definitions wrapped in
* anonymous functions.</li>
* <li><a href="http://www.jslint.com/">jslint</a>. See also the very latest jslint source at
* <a href="https://github.com/douglascrockford/JSLint">Crockford's GitHub repository</a>. There is no
* longer a Rhino version on jslint.com but a major update to jslint was posted 6 Jan 2011 so it may
* just be a matter of a little time. The new fulljslint.js provides a JSLint function that returns
* an object with the errors as a member, so a small wrapper will be needed for use with Rhino.</li>
* <li><a href="http://www.mozilla.org/rhino/">Rhino</a> (needed for standalone command-line execution of jslint under Java)</li>
* </ul>
* </p>
* <h3>Makefile Adjustments</h3>
* You will need to configure the Makefile to set the installed locations of these tools:
* <ul>
* <li>YUI Compressor</li>
* <li>YUI Doc</li>
* <li>Rhino</li>
* <li>JSLint</li>
* </ul>
* There is a set of Make variables defined at the top of the Makefile whose meanings should be self-evident.
*
* @module scalecalc
* @author Copyright 2009,2010,2011 by Dave Cook dcook@caveduck.com
* @version 1.0.1
*/
/*jslint browser: true */
/*global $, Dom, Utils, window, DomElement, Div, Table, InputButton, InputText, Selector, Option, TextArea */
(function () {
var U = Utils;
/**
* Constructs a new ScaleCalc scale model calculator gadget.
* @class ScaleCalc
*/
/*global ScaleCalc:true*/
ScaleCalc = function () {
var T = this, SC = ScaleCalc, CU = Const.Units, i, pl, sl, tmp, sselopts, prefs, unitsopt;
T.id = U.uniqueID("ScaleCalc");
SC.loadPrefs();
prefs = SC.prefs;
T.precision = 5;
T.ratioText = new InputText(".scaleFactorScaleRatioText", prefs.userDefScale, 8);
Dom.setTitle(T.ratioText, SC.i18n("RatioText_hover"));
Dom.addHandler(T.ratioText, "onchange", T.ratioTextChange, T);
// Set language-specific content of headings and labels
T.setLocaleStrings();
// Scale domain selector
sselopts = [
{key: "All_opt", val: "all"},
{key: "Model_RR_opt", val: "rr"},
{key: "Plastic_Models_opt", val: "pm"},
{key: "Slot_Cars_opt", val: "slot"},
{key: "Dollhouse_opt", val: "doll"}
];
T.scaleSetSel = new Selector(".scaleFactorDomainSel", SC.i18n("Modeling_Domain_hover"), null, sselopts, SC.TT);
tmp = T.scaleSetSel;
tmp.setSelectedValue(prefs.scaleSet);
Dom.addHandler(tmp, "onchange", T.scaleSetSelOnchange, T);
// Scale selector
T.scSel = new Selector(".scaleFactorScaleName", SC.i18n("Scale_Name_hover"));
Dom.setSize(T.scSel, "10em");
Dom.addHandler(T.scSel, "onchange", T.scSelOnchange, T);
// Units list with i18n keyword and selector val
unitsopt = [
{key: "inches", val: "in"},
{key: "feet", val: "ft"},
{key: "millimeters", val: "mm"},
{key: "centimeters", val: "cm"},
{key: "meters", val: "m"}
];
// prototype units selector
T.protoUnitsSel = new Selector(".protoUnitsSel", SC.i18n("PROTOTYPE_UNITS_hover"), null, unitsopt, SC.TT);
tmp = T.protoUnitsSel;
tmp.setMultiSelect(true);
tmp.setNumVis(5);
tmp.setOptionSelect(0, false);
pl = prefs.protoUnitsList.split(",");
for (i = 0; i < pl.length; i += 1) {
tmp.setOptionSelect(pl[i], true);
}
Dom.addHandler(tmp, "onchange", T.protoUnitsSelOnchange, T);
// scale units selector
T.scaleUnitsSel = new Selector(".modelUnitsSel", SC.i18n("SCALE_UNITS_hover"), null, unitsopt, SC.TT);
tmp = T.scaleUnitsSel;
tmp.setMultiSelect(true);
tmp.setNumVis(5);
tmp.setOptionSelect(0, false);
sl = prefs.scaleUnitsList.split(",");
for (i = 0; i < sl.length; i += 1) {
tmp.setOptionSelect(sl[i], true);
}
Dom.addHandler(tmp, "onchange", T.scaleUnitsSelOnchange, T);
// Load scale selector values and make scSel and ratioText consistent
T.scaleSetSelOnchange();
// static numeric fields that get assigned to dynamic field lists
T.fld_R_in = T.createScaleField(8, true, CU.IN_TO_M, SC.i18n("UNITS_IN"));
T.fld_R_ft = T.createScaleField(8, true, CU.FT_TO_M, SC.i18n("UNITS_FT"));
T.fld_R_mm = T.createScaleField(8, true, CU.MM_TO_M, SC.i18n("UNITS_MM"));
T.fld_R_cm = T.createScaleField(8, true, CU.CM_TO_M, SC.i18n("UNITS_CM"));
T.fld_R_m = T.createScaleField(8, true, 1, SC.i18n("UNITS_M"));
T.fld_S_in = T.createScaleField(8, false, CU.IN_TO_M, SC.i18n("UNITS_IN"));
T.fld_S_ft = T.createScaleField(8, false, CU.FT_TO_M, SC.i18n("UNITS_FT"));
T.fld_S_mm = T.createScaleField(8, false, CU.MM_TO_M, SC.i18n("UNITS_MM"));
T.fld_S_cm = T.createScaleField(8, false, CU.CM_TO_M, SC.i18n("UNITS_CM"));
T.fld_S_m = T.createScaleField(8, false, 1, SC.i18n("UNITS_M"));
// prototype and scale dimensions tables
T.subtab1 = new Table(null, 1, 12);
T.subtab2 = new Table(null, 1, 12);
T.subtab1.setCell(0, 0, {content: SC.i18n("REAL"), sclass: "bold dimLabel"});
T.subtab2.setCell(0, 0, {content: SC.i18n("MODEL"), sclass: "bold dimLabel"});
T.protoDimSpan = new DomElement(".prototypeDimensionSpan", 'span', T.subtab1);
T.protoDimSpan.jq.addClass('scRealRow');
T.modelDimSpan = new DomElement(".modelDimensionSpan", 'span', T.subtab2);
T.modelDimSpan.jq.addClass('scScaleRow');
// Run proto & scale units selector change handlers to generate arrays of field refs
// and assign the active fields to display table cells
T.R_fields = [];
T.S_fields = [];
T.fields = [];
T.protoUnitsSelOnchange();
T.scaleUnitsSelOnchange();
// Item description label and field
T.descripText = new InputText(".itemDescription", "", 50);
Dom.setTitle(T.descripText, SC.i18n("Description_hover"));
// record button
T.recordButton = new InputButton('.recordButton', false, SC.i18n("Record"));
Dom.setTitle(T.recordButton, SC.i18n("Record_hover"));
Dom.addHandler(T.recordButton, "onclick", T.recordButtonOnclick, T);
// clear button
T.clearButton = new InputButton('.clearButton', false, SC.i18n("Clear"));
Dom.setTitle(T.clearButton, SC.i18n("Clear_hover"));
Dom.addHandler(T.clearButton, "onclick", T.clearButtonOnclick, T);
// output mode selector
T.outModeSel = new Selector(".outputFmtSel", SC.i18n("Output_Format_hover"));
tmp = T.outModeSel;
tmp.addOption(new Option(SC.i18n("Text"), "text"));
tmp.addOption(new Option(SC.i18n("CSV"), "csv"));
tmp.setSelectedValue(prefs.outputMode);
Dom.addHandler(tmp, "onchange", T.outModeSelOnchange, T);
// textarea for "calculator tape" history
T.calcTape = new TextArea(".outputTextarea");
tmp = T.calcTape;
tmp.setAreaSize(15, 90);
tmp.setValue(prefs.history);
// Set up onchange handlers for the dimension fields
for (i = 0; i < T.fields.length; i += 1) {
Dom.addHandler(T.fields[i], "onchange", T.scFldChange, T.fields[i]);
}
console.log("ScaleCalc init OK\n");
};
var SC = ScaleCalc;
SC.fn = SC.prototype;
/**
* Preferences object, loaded from a cookie.
* @static
* @type Object
*/
SC.prefs = {
protoUnitsList: "ft,m",
scaleUnitsList: "in,cm",
userDefScale: "48",
scaleSet: "all",
history: "",
outputMode: "text"
};
/**
* Limit on length of calc tape history stored in prefs.
* @type number
* @static
*/
SC.maxCalcHistoryLength = 1024;
/**
* Name of the gadget's cookie
* @static
* @type string
*/
SC.cookieName = "scaleCalc";
/**
* Loads preferences from the gadget's cookie, using a non-recursive merge.
* @static
*/
SC.loadPrefs = function () {
var ob, str, ck;
ck = new U.Cookie(SC.cookieName);
ob = ck.getObj();
if (ob) {
$.extend(SC.prefs, ob);
}
};
/**
* Saves preferences to the gadget's cookie
* @static
*/
SC.savePrefs = function () {
var ck = new U.Cookie(SC.cookieName);
ck.set(SC.prefs, 60);
};
/**
* Wrapper for Utils.i18n to eliminate first arg
* @static
*/
SC.i18n = function (k1, k2) {
return U.i18n(SC.TT, k1, k2);
};
/**
* Sets language-specific content of headings and labels. Elements are selected by
* their unique classname in the layout file.
*/
SC.fn.setLocaleStrings = function () {
$('.scaleCalcH1-title').html(SC.i18n("Scale_Model_Calculator"));
$('.legend_scaleFactor').html(SC.i18n("Scale_Factor"));
$('.label_modelingDomain').html(SC.i18n("Modeling_Domain"));
$('.label_scaleName').html(SC.i18n("Scale_Name"));
$('.label_scaleRatio').html(SC.i18n("Scale_Ratio"));
$('.legend_unitsToDisplay').html(SC.i18n("Units_to_Display"));
$('.label_prototypeUnits').html(SC.i18n("Prototype_Units"));
$('.label_modelUnits').html(SC.i18n("Model_Units"));
$('.legend_item').html(SC.i18n("Item"));
$('.label_description').html(SC.i18n("Description"));
$('.legend_listing').html(SC.i18n("Listing"));
$('.label_outputFormat').html(SC.i18n("Output_Format"));
};
/**
* Returns the top level DOM element for this gadget. Required API for Dom utilities.
*/
SC.fn.getElmt = function () {
return this.tab.getElmt();
};
/**
* Appends current calculated values to the calculator tape TextArea
*/
SC.fn.recordButtonOnclick = function () {
var T = this, i, txt, omode, ct, dt, pad = " ", ntxt = "", hv, mlen,
R_fields = T.R_fields, S_fields = T.S_fields;
ct = T.calcTape;
txt = ct.getValue();
omode = T.outModeSel.getSelectedValue();
// emit CSV header as first line when output history is blank
if ((txt.length === 0) && (omode === "csv")) {
ntxt += "Description,Ratio,";
for (i = 0; i < R_fields.length; i += 1) {
ntxt += "Proto_" + R_fields[i].units + ",";
}
for (i = 0; i < S_fields.length; i += 1) {
ntxt += "Scale_" + S_fields[i].units + ",";
}
ntxt += "\n";
}
// Output the current values to the history textarea
if (omode === "text") {
ntxt += T.descripText.getValue() + " " + SC.i18n("SCALE_TAPE") +
T.ratioText.getValue() + ")\n";
pad = " ";
ntxt += SC.i18n("REAL_TAPEHDR");
for (i = 0; i < R_fields.length; i += 1) {
ntxt += R_fields[i].getValue() + " " + R_fields[i].units + pad;
}
ntxt += "\n";
ntxt += SC.i18n("MODEL_TAPEHDR");
for (i = 0; i < S_fields.length; i += 1) {
ntxt += S_fields[i].getValue() + " " + S_fields[i].units + pad;
}
ntxt += "\n";
} else if (omode === "csv") {
// :TODO: number formatting on numeric fields to avoid locale probs with vals like 3,000
// surround description with double quotes in case it contains commas
dt = "\"" + T.descripText.getValue() + "\"";
ntxt += dt + "," + T.ratioText.getValue() + ",";
for (i = 0; i < R_fields.length; i += 1) {
ntxt += R_fields[i].getValue() + ",";
}
for (i = 0; i < S_fields.length; i += 1) {
ntxt += S_fields[i].getValue() + ",";
}
ntxt += "\n";
}
ct.setValue(txt + ntxt);
ct.scrollToBottom();
// We limit the amount of history saved due to cookie size limit
hv = ct.getValue();
mlen = SC.maxCalcHistoryLength;
SC.prefs.history = hv.length > mlen ? hv.substr(hv.length - mlen) : hv;
SC.savePrefs();
};
/**
* Handler to clear the history TextArea
*/
SC.fn.clearButtonOnclick = function () {
this.calcTape.setValue("");
SC.prefs.history = this.calcTape.getValue();
SC.savePrefs();
};
/**
* Updates numeric and units display cells from the field arrays
*/
SC.fn.updateDisplayCells = function () {
var T = this, i, j, st1 = T.subtab1, st2 = T.subtab2;
// Nuke existing contents of display subtabs and remove width style
for (i = 1; i < st1.getNumCols(); i += 1) {
st1.setCell(0, i, {content: "", width: 0});
}
for (i = 1; i < st2.getNumCols(); i += 1) {
st2.setCell(0, i, {content: "", width: 0});
}
// Poke in new contents
for (i = 0, j = 1; i < T.R_fields.length; i += 1, j += 2) {
st1.setCell(0, j, {content: T.R_fields[i], align: "center"});
st1.setCell(0, j + 1, {content: T.R_fields[i].units, align: "left", sclass: "bold", width: "3em"});
}
for (i = 0, j = 1; i < T.S_fields.length; i += 1, j += 2) {
st2.setCell(0, j, {content: T.S_fields[i], align: "center"});
st2.setCell(0, j + 1, {content: T.S_fields[i].units, align: "left", sclass: "bold", width: "3em"});
}
};
/**
* Creates an InputText wrapper with some extra properties for scale conversion machinery:
* <ul>
* <li>Numeric value for the computed size.
* <li>Conversion factor that converts the field's numeric value to meters.
* <li>Flag specifying whether this is an on-model or real-world field.
* <li>A back-link to the parent object for use in event handlers.
* </ul>
* @param {number} siz Size of the InputText field in chars.
* @param {boolean} bReal True if this a "real-world" measurement, false for scale measurement.
* @param {number} toMeters Conversion factor that carries the field's value to meters.
* @param {number} units Units of measure string for the field.
* @param {number} val Initial value for the field. Can be omitted for all but one of the fields.
* @return {InputText} Returns an augmented InputText widget.
*/
SC.fn.createScaleField = function (siz, bReal, toMeters, units, val) {
var fld = new InputText(null, "", siz);
fld.bReal = bReal;
fld.toMeters = toMeters;
fld.units = units;
fld.parent = this;
fld.val = (U.isnum(val) ? val : 0);
fld.setValue(fld.val.toPrecision(this.precision));
return fld;
};
/**
* Rebuilds the composite fields array by concatenating all the members of R_fields and S_fields.
* Also forces the first field -- which is always an R_field -- to have a nonzero value so
* that recomputeFields will work correctly during initial setup.
*/
SC.fn.rebuildFieldsArr = function () {
var T = this, i = 0, j;
T.fields = [];
for (j = 0; j < T.R_fields.length; j += 1) {
T.fields[i++] = T.R_fields[j];
}
for (j = 0; j < T.S_fields.length; j += 1) {
T.fields[i++] = T.S_fields[j];
}
// Make sure the first field has a nonzero numeric value so recomputeFields will always work
if (T.fields && (T.fields.length > 0)) {
if ((typeof T.fields[0].val !== "number") || (T.fields[0].val === 0)) {
T.fields[0].val = 1.0;
T.fields[0].setValue(T.fields[0].val.toPrecision(T.precision));
}
}
};
/**
* Onchange handler for the prototype units selector. Rebuilds the R_fields array
* to contain just the current selection set.
*/
SC.fn.protoUnitsSelOnchange = function () {
var T = this, i, ls, nref, vals = T.protoUnitsSel.getAllSelectedValues();
// Rebuid R_fields array with just the selected ones
T.R_fields = [];
ls = "";
for (i = 0; i < vals.length; i += 1) {
if (i !== 0) {
ls += ",";
}
ls += vals[i];
nref = null;
switch (vals[i]) {
case "in":
nref = T.fld_R_in;
break;
case "ft":
nref = T.fld_R_ft;
break;
case "mm":
nref = T.fld_R_mm;
break;
case "cm":
nref = T.fld_R_cm;
break;
case "m":
nref = T.fld_R_m;
break;
}
if (nref) {
T.R_fields[i] = nref;
}
}
T.rebuildFieldsArr();
T.updateDisplayCells();
T.recomputeFields(T.fields[0]);
SC.prefs.protoUnitsList = ls;
SC.savePrefs();
};
/**
* Onchange handler for the scale units selector. Rebuilds the S_fields array
* to contain just the current selection set.
*/
SC.fn.scaleUnitsSelOnchange = function () {
// get array of selected Option value strings
var T = this, i, nref, ls, vals = T.scaleUnitsSel.getAllSelectedValues();
// Rebuid S_fields array with just the selected ones
T.S_fields = [];
ls = "";
for (i = 0; i < vals.length; i += 1) {
if (i !== 0) {
ls += ",";
}
ls += vals[i];
nref = null;
switch (vals[i]) {
case "in":
nref = T.fld_S_in;
break;
case "ft":
nref = T.fld_S_ft;
break;
case "mm":
nref = T.fld_S_mm;
break;
case "cm":
nref = T.fld_S_cm;
break;
case "m":
nref = T.fld_S_m;
break;
}
if (nref) {
T.S_fields[i] = nref;
}
}
T.rebuildFieldsArr();
T.updateDisplayCells();
T.recomputeFields(T.fields[0]);
SC.prefs.scaleUnitsList = ls;
SC.savePrefs();
};
/**
* Option key/value array for model railroad scales.
* @static
* @type array of anonymous objects
*/
SC.opts_modelRR = [
{'key': 'GScale_opt', 'val': '22.5'},
{'key': 'Gauge1_opt', 'val': '32'},
{'key': 'OScale_opt', 'val': '48'},
{'key': 'SGauge_opt', 'val': '64'},
{'key': 'HOScale_opt', 'val': '87'},
{'key': 'TTScale_opt', 'val': '120'},
{'key': 'NScale_opt', 'val': '160'},
{'key': 'ZScale_opt', 'val': '220'}
];
/**
* Option key/value array for common plastic model scales
* @static
* @type array of anonymous objects
*/
SC.opts_PM_common = [
{'key': '1/12', 'val': '12'},
{'key': '1/24', 'val': '24'},
{'key': '1/32', 'val': '32'},
{'key': '1/35', 'val': '35'},
{'key': '1/48', 'val': '48'},
{'key': '1/72', 'val': '72'},
{'key': '1/144', 'val': '144'},
{'key': '1/350', 'val': '350'},
{'key': '1/700', 'val': '700'}
];
/**
* Option key/value array for dollhouse scales
* @static
* @type array of anonymous objects
*/
SC.opts_dollhouse = [
{'key': 'DollhouseScale_opt', 'val': '12'},
{'key': 'HalfDollhouseScale_opt', 'val': '24'}
];
/**
* Option key/value array for slot car scales
* @static
* @type array of anonymous objects
*/
SC.opts_slotcar = [
{'key': '1/24', 'val': '24'},
{'key': '1/32', 'val': '32'},
{'key': '1/43', 'val': '43'},
{'key': '1/64', 'val': '64'},
{'key': 'HOScale_opt', 'val': '87'}
];
/**
* Option key/value array for unique slot car scales that don't appear in the PM or MRR scale lists
* @static
* @type array of anonymous objects
*/
SC.opts_slotcar_unique = [
{'key': '1/43', 'val': '43'},
{'key': '1/64', 'val': '64'} // snaps to S Guage MRR scale
];
/**
* Onchange handler for the scale set selector. Rebuilds the scSel Selector
* with scales from the newly selected set, and re-scans for the selected
* scale within the set based on the ratio stored in the prefs.
*/
SC.fn.scaleSetSelOnchange = function () {
var T = this, scaleset = T.scaleSetSel.getSelectedValue();
T.scSel.removeAll();
// userdef Option must come first in all sets
T.scSel.addOption(new Option(SC.i18n("UserDefined_opt"), ""));
switch (scaleset) {
case "rr":
T.scSel.addOptions(SC.opts_modelRR, SC.TT);
break;
case "all":
// option groups for composite menu
T.scSel.addOptionGroup(new OptionGroup(SC.opts_modelRR, SC.TT, SC.i18n("Model_RR_opt")));
T.scSel.addOptionGroup(new OptionGroup(SC.opts_PM_common, SC.TT, SC.i18n("Plastic_Models_opt")));
T.scSel.addOptionGroup(new OptionGroup(SC.opts_slotcar, SC.TT, SC.i18n("Slot_Cars_opt")));
T.scSel.addOptionGroup(new OptionGroup(SC.opts_dollhouse, SC.TT, SC.i18n("Dollhouse_opt")));
break;
case "pm":
T.scSel.addOptions(SC.opts_PM_common, SC.TT);
break;
case "doll":
T.scSel.addOptions(SC.opts_dollhouse, SC.TT);
break;
case "slot":
T.scSel.addOptions(SC.opts_slotcar, SC.TT);
break;
}
// update ratio text and set selected scSel item from prefs scale factor
T.updateScaleFromPrefs();
SC.prefs.scaleSet = scaleset;
SC.savePrefs();
};
/**
* Updates the scale selector and ratio text from the prefs ratio field.
*/
SC.fn.updateScaleFromPrefs = function () {
var T = this, udscale, selIndx;
udscale = SC.prefs.userDefScale;
T.ratioText.setValue(udscale);
// Look up prefs scale against the scSel values and set the selector index
// If not found, change scSel to user-defined
selIndx = T.scSel.getOptionIndexByValue(udscale);
if (selIndx === null) {
T.scSel.setSelectedIndex(0);
} else {
T.scSel.setSelectedIndex(selIndx);
}
};
/**
* Onchange handler for the scale ratio Selector. Recomputes all of the field
* values in the newly selected scale. We really only need to update the on-model
* fields but it's easier to just launch a full recompute.
*/
SC.fn.scSelOnchange = function () {
var T = this, curIndx = T.scSel.getSelectedIndex();
if (curIndx !== 0) {
T.ratioText.setValue(T.scSel.getSelectedValue());
// base the recompute on any real-world field since changing the ratio only
// affects the on-model values
T.recomputeFields(T.fields[0]);
SC.prefs.userDefScale = T.ratioText.getValue();
SC.savePrefs();
}
// Disallow meaningless manual change of the selector to user-defined
// Re-scan for the item matching the ratio and put the selector back
T.updateScaleFromPrefs();
};
/**
* Onchange handler for all the InputText scale fields. Recomputes all the field
* values based on the new value of the changed field.
*/
SC.fn.scFldChange = function () {
// 'this' must be the InputText wrapper
this.parent.recomputeFields(this);
};
/**
* Onchange handler for the user-defined scale ratio InputText field.
* Changes the Selector value to "user-defined" and recomputes the scale field values.
*/
SC.fn.ratioTextChange = function () {
var T = this, selIndx = T.scSel.getOptionIndexByValue(T.ratioText.getValue());
T.scSel.setSelectedIndex(selIndx ? selIndx : 0);
T.recomputeFields(T.fields[0]);
SC.prefs.userDefScale = T.ratioText.getValue();
SC.savePrefs();
};
/**
* Recalcs the numeric dimension fields. All of the fields are recomputed to
* match the (changed) value in <i>srcFld</i> via the conversion-to-meters factor
* stored in each field object and the scale ratio.
* @param srcFld Changed field used as the basis to compute the others.
*/
SC.fn.recomputeFields = function (srcFld) {
var T = this, i, ratio, srcm, dstToMeters;
ratio = parseFloat(T.ratioText.getValue());
// Convert text to numeric value in edited field
srcFld.val = parseFloat(srcFld.getValue());
srcFld.setValue(srcFld.val.toPrecision(T.precision));
srcm = srcFld.val * srcFld.toMeters;
for (i = 0; i < T.fields.length; i += 1) {
if (T.fields[i].id !== srcFld.id) {
dstToMeters = T.fields[i].toMeters;
// convert src value to this field per scale factors
if (srcFld.bReal && T.fields[i].bReal) {
// real-to-real
T.fields[i].val = srcm / dstToMeters;
} else if (!srcFld.bReal && !T.fields[i].bReal) {
// model-to-model
T.fields[i].val = srcm / dstToMeters;
} else if (srcFld.bReal) {
// real-to-model
T.fields[i].val = srcm / ratio / dstToMeters;
} else {
// model-to-real
T.fields[i].val = srcm * ratio / dstToMeters;
}
T.fields[i].setValue(T.fields[i].val.toPrecision(T.precision));
}
}
};
/**
* Onchange handler for history output mode selector. Saves the new value to the prefs.
*/
SC.fn.outModeSelOnchange = function () {
SC.prefs.outputMode = this.outModeSel.getSelectedValue();
SC.savePrefs();
};
/**
* Language table - English
* @static
*/
SC.TT_en = {
'Scale_Model_Calculator': "Scale Model Dimension Calculator",
'Scale_Factor': "Scale Factor",
'Modeling_Domain': "Modeling Domain",
'Scale_Name': "Scale Name",
'Scale_Ratio': "Scale Ratio",
'Units_to_Display': "Units to Display",
'Prototype_Units': "Prototype Units",
'Model_Units': "Model Units",
'Item': "Item",
'Description': "Description",
'Listing': "Listing",
'Output_Format': "Output Format",
REAL: "PROTOTYPE",
MODEL: "SCALE",
REAL_TAPEHDR: " PROTOTYPE: ",
MODEL_TAPEHDR: " SCALE : ",
SCALE_TAPE: "(Scale 1:",
'Modeling_Domain_hover': "Select modeling domain to see typical scales in that domain",
'Scale_Name_hover': "Select a modeling scale by name",
PROTOTYPE_UNITS_hover: "Select one or more units for prototype dimensions",
SCALE_UNITS_hover: "Select one or more units for scale dimensions",
'Output_Format_hover': "Select format for text listing",
Record: "Record",
Record_hover: "Click to write current values to the history",
Clear: "Clear",
Clear_hover: "Click to clear the history",
Description_hover: "Enter description for calculator tape output",
RatioText_hover: "Enter custom scale factor as a number, e.g. 32, 48, 72.5, etc",
// scale domain sets
All_opt: "All Scales",
Model_RR_opt: "Model Railroad",
Plastic_Models_opt: "Plastic Model",
Slot_Cars_opt: "Slot Car",
Dollhouse_opt: "Dollhouse",
// named scales
UserDefined_opt: "User-defined",
GScale_opt: "G Scale",
Gauge1_opt: "1 Gauge",
OScale_opt: "O Scale",
SGauge_opt: "S Gauge",
HOScale_opt: "HO Scale",
TTScale_opt: "TT Scale",
NScale_opt: "N Scale",
ZScale_opt: "Z Scale",
DollhouseScale_opt: "Dollhouse Scale",
HalfDollhouseScale_opt: "Half Dollhouse Scale",
// Units lowercase abbrevs
UNITS_IN: "in",
UNITS_FT: "ft",
UNITS_M: "m",
UNITS_CM: "cm",
UNITS_MM: "mm",
// Units spelled out in lowercase
inches: "inches",
feet: "feet",
millimeters: "millimeters",
centimeters: "centimeters",
meters: "meters",
// Output modes
Text: "Text",
CSV: "CSV"
};
/**
* Language table selector
* @static
*/
SC.TT = SC.TT_en;
/**
* Instantiates a calculator widget and binds to document elements
* @static
*/
SC.main = function () {
Utils.setupFirebugAPI();
var scalc = new SC();
};
contentLoaded(window, SC.main);
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment