Template:Interactive calculator/doc

This template calculates a Tax Withholding Ratio (TWR) for an ETF when held by a US nonresident alien, from values entered into the page by the user. The definition of TWR is:

TWR = (Yield × L1TW) + ((Yield × (1 - L1TW) - TER) × L2TW)

Where:
 * L1TW: Percentage of tax withholding by a security's home country on dividends distributed by that security to the fund (Level 1).
 * L2TW: Percentage of tax withholding by the country where the fund is domiciled on the dividends distributed to the investor by the fund (Level 2).
 * Yield: Annual yield of the fund.
 * TER: The fund's Total Expense Ratio.

This template uses JavaScript to provide live in-page calculations. The computed TWR is updated on pressing the 'Calculate' button, or when the user changes any of the input values.

Usage
Invoke with:

This template takes no arguments. The default input values are currently hardcoded. This template may be transcluded multiple times on a single page.

Technical notes
The JavaScript code is split into two parts. The piece that defines the wanted inputs and the calculation done on them is in the  pseudo-namespace. The rest of it is just 'plumbing' to create and update screen elements.

Main approach

 * defines the input fields (id, name, initial value, validator pattern, and output format), and a single output (result) field. The result must be the last listed, and have no initial value or pattern. It defines the calculation that generates the result.
 * On loading the page,  creates an HTML table element containing input fields and a 'Calculate' button, from data in  . It adds this to the DOM, at the script location. Elements in the table are tagged with an ID that is unique to this invocation.
 * Updating an input field or clicking on 'Calculate' generates a call to . This validates the inputs, runs the required calculation, then updates all the fields. Invalid inputs generate NaN, displayed as 'Error!'.

Multiple transclusion

 * Global (cross-script) data is stored in . Members are a transclusion counter, and a data lookup table for use by.
 * Support for more than one transclusion in a page is provided by generating unique identifiers for html input and output elements. The unique identifiers include a sequence number that increments on each transclusion.
 * On update or 'Calculate',  receives an argument containing the prefix for the unique html element identifiers. From this, it can deduce which data set it should use and which html elements to work with.

Code reusability

 * A single instance of  can support multiple instances of transclusion. This is a requirement, since it is effectively redefined each time.
 * It can also support multiple different schemas, where  is one of many possible schemas. This allows the code to be quickly repurposed into a different type of calculator. For example, the following schema will create a monthly compound interest calculator:
 * Alternative implementations or versions of  are possible (for example,  ). Implementations need to hold a   function, have a   that matches their pseudo-namespace, and be able to work with schemas and with.

Portability

 * The current code uses the minimum required set of JavaScript features. It consciously avoids any features that might not be present in some older browsers.
 * MediaWiki's Mobile frontend/skin creates CSS styles that do not display well with  and   HTML elements. To improve things, these elements are explicitly styled to appear broadly similar in both Desktop and Mobile views.

Coding restrictions

 * MediaWiki does not "play well" with  elements. Several aspects of this code are designed to avoid triggering MediaWiki into replacing text inside the script that will either stop it working, or at worst render it into invalid JavaScript syntax. The current known problem areas are:
 * Conditional expressions. MediaWiki will convert  into  . This is no longer syntactically valid JavaScript. Workround is to not use this feature.
 * Ampersand. MediaWiki will convert  into  . This is no longer syntactically valid JavaScript. Workround is to use nested conditionals.
 * Some HTML tags. MediaWiki may entirely remove a selection of HTML tags from inside the script, even when within quoted strings. For example,  turns into the empty string,  . Likewise   and  ' . The result is syntactically valid, but will fail to execute properly. The current implementation tackles this by using '[' and ']' to enclose tags, and then globally replacing them with '<' and '>' on output. This is simple, but rather clumsy.

Accuracy/regression tests
 (function {  var write = function(string) {    document.write(string);  };

var prefix = '';

var initialize = function { if (!('calc_global' in window)) { write(' No calculator globals found on this page.\n '); return false; }   for (var candidate in window.calc_global) { if (candidate == 'counter') continue; if (candidate > prefix) prefix = candidate; }   if (prefix == '') { write(' No calculators found on this page.\n '); return false; }   if (!document.getElementById(prefix + 'result')) { write(' Invalid prefix found; tests cannot run.\n '); return false; }   return true; };

var setup = function(values) { for (var id in values) { var element = document.getElementById(prefix + id); element.value = values[id]; } };

var execute = function(schema) { calc_v1.calculate(prefix, schema); var element = document.getElementById(prefix + 'result'); return element.value; };

var passed = 0, failed = 0; var record = function(values, result, expect) { var outcome; if (result == expect) { passed++; outcome = 'PASS'; } else { failed++; outcome = 'FAIL'; }   write('Test:' + JSON.stringify(values)          + ', result:' + result + ', expect:' + expect + ', ' + outcome + '\n'); };

var run_test = function(values, expect) { setup(values); record(values, execute('twr'), expect); };

if (initialize) { write(' '); run_test({l1tw: 0, l2tw: 30, yield: 1.74, ter: 0.03}, '0.5130%'); run_test({l1tw: 15, l2tw: 0, yield: 1.74, ter: 0.07}, '0.2610%'); run_test({l1tw: 3.9, l2tw: 30, yield: 2, ter: 0.08}, '0.6306%'); run_test({l1tw: 10.3, l2tw: 0, yield: 2, ter: 0.22}, '0.2060%'); run_test({l1tw: 0, l2tw: 0, yield: 0, ter: 0.1},     'Error!'); var tested = passed + failed; write('Tested:' + tested + ', passed:' + passed + ', failed:' + failed + '\n'); write(' '); var t = 'Tested!'; setup({l1tw: t, l2tw: t, yield: t, ter: t, result: t}); } });