Template talk:Interactive calculator

Accuracy and regression tests
The following section tests the basic functionality of TWR Calculator. All tests should PASS, and 'Tested!' should appear in the calculator input and output boxes.

 var bh_calculator_test = "Ly8gU3RhcnQgQmFzZTY0IGVuY29kaW5nIGhlcmUuCihmdW5jdGlvbigpIHsKICB2YXIgd3JpdGUgPSBmdW5jdGlvbihzdHJpbmcpIHsKICAgIGRvY3VtZW50LndyaXRlKCI8cHJlPiIgKyBzdHJpbmcgKyAiXG48L3ByZT4iKTsKICB9OwoKICB2YXIgcHJlZml4ID0gIiI7CiAgdmFyIHNjaGVtYSA9ICIiOwoKICB2YXIgaW5pdGlhbGl6ZSA9IGZ1bmN0aW9uKHNjaGVtYV8pIHsKICAgIGlmICghKCJiaF8iIGluIHdpbmRvdykgfHwgISgiY2FsY3VsYXRvcl9nbG9iYWwiIGluIGJoXykpIHsKICAgICAgd3JpdGUoIk5vIGNhbGN1bGF0b3IgZ2xvYmFscyBmb3VuZCBvbiB0aGlzIHBhZ2U7IHRlc3RzIGNhbm5vdCBydW4uIik7CiAgICAgIHJldHVybiBmYWxzZTsKICAgIH0KICAgIGZvciAodmFyIGNhbmRpZGF0ZSBpbiBiaF8uY2FsY3VsYXRvcl9nbG9iYWwpIHsKICAgICAgaWYgKGNhbmRpZGF0ZSAhPSAiY291bnRlciIgJiYgY2FuZGlkYXRlID4gcHJlZml4KQogICAgICAgIHByZWZpeCA9IGNhbmRpZGF0ZTsKICAgIH0KICAgIGlmIChwcmVmaXggPT0gIiIpIHsKICAgICAgd3JpdGUoIk5vIGNhbGN1bGF0b3JzIGZvdW5kIG9uIHRoaXMgcGFnZTsgdGVzdHMgY2Fubm90IHJ1bi4iKTsKICAgICAgcmV0dXJuIGZhbHNlOwogICAgfQogICAgaWYgKCFkb2N1bWVudC5nZXRFbGVtZW50QnlJZChwcmVmaXggKyAiI2J1dHRvbiIpKSB7CiAgICAgIHdyaXRlKCJQcmVmaXggJyIgKyBwcmVmaXggKyAiJyBpcyBpbnZhbGlkOyB0ZXN0cyBjYW5ub3QgcnVuLiIpOwogICAgICByZXR1cm4gZmFsc2U7CiAgICB9CiAgICBpZiAoIShzY2hlbWFfIGluIGJoXykpIHsKICAgICAgd3JpdGUoIlNjaGVtYSAnIiArIHNjaGVtYV8gKyAiJyBub3QgZm91bmQ7IHRlc3RzIGNhbm5vdCBydW4uIik7CiAgICAgIHJldHVybiBmYWxzZTsKICAgIH0KICAgIHNjaGVtYSA9IHNjaGVtYV87CiAgICByZXR1cm4gdHJ1ZTsKICB9OwoKICB2YXIgc2V0dXAgPSBmdW5jdGlvbih2YWx1ZXMpIHsKICAgIGZvciAodmFyIGlkIGluIHZhbHVlcykgewogICAgICB2YXIgZWxlbWVudCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKHByZWZpeCArIGlkKTsKICAgICAgZWxlbWVudC52YWx1ZSA9IHZhbHVlc1tpZF07CiAgICB9CiAgfTsKCiAgdmFyIGV4ZWN1dGUgPSBmdW5jdGlvbigpIHsKICAgIHZhciBlbGVtZW50ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQocHJlZml4ICsgIiNidXR0b24iKTsKICAgIHZhciBvbmNsaWNrID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoIm9uY2xpY2siKTsKICAgIGV2YWwob25jbGljayk7CiAgICBlbGVtZW50ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQocHJlZml4ICsgInJlc3VsdCIpOwogICAgcmV0dXJuIGVsZW1lbnQudmFsdWU7CiAgfTsKCiAgdmFyIHBhc3NlZCA9IDAsIGZhaWxlZCA9IDA7CiAgdmFyIHJlc3VsdHMgPSBbXTsKCiAgdmFyIHJlY29yZCA9IGZ1bmN0aW9uKHZhbHVlcywgcmVzdWx0LCBleHBlY3QpIHsKICAgIHZhciBvdXRjb21lID0gKHJlc3VsdCA9PSBleHBlY3QpOwogICAgcGFzc2VkICs9IG91dGNvbWU7CiAgICBmYWlsZWQgKz0gIW91dGNvbWU7CiAgICB2YXIgcmVwb3J0ID0gIlRlc3Q6IiArIEpTT04uc3RyaW5naWZ5KHZhbHVlcykKICAgICAgICAgICAgICAgKyAiLCByZXN1bHQ6IiArIHJlc3VsdCArICIsIGV4cGVjdDoiICsgZXhwZWN0ICsgIiwgIgogICAgICAgICAgICAgICArIChvdXRjb21lID8gIlBBU1MiIDogIkZBSUwiKTsKICAgIHJlc3VsdHMucHVzaChyZXBvcnQpOwogIH07CgogIHZhciBydW5fdGVzdCA9IGZ1bmN0aW9uKHZhbHVlcywgZXhwZWN0KSB7CiAgICBzZXR1cCh2YWx1ZXMpOwogICAgcmVjb3JkKHZhbHVlcywgZXhlY3V0ZSgpLCBleHBlY3QpOwogIH07CgogIGlmIChpbml0aWFsaXplKCJjYWxjdWxhdG9yX3R3ciIpKSB7CiAgICBydW5fdGVzdCh7bDF0dzogMCwgbDJ0dzogMzAsIHlpZWxkOiAxLjc0LCB0ZXI6IDAuMDN9LCAiMC41MTMwJSIpOwogICAgcnVuX3Rlc3Qoe2wxdHc6IDE1LCBsMnR3OiAwLCB5aWVsZDogMS43NCwgdGVyOiAwLjA3fSwgIjAuMjYxMCUiKTsKICAgIHJ1bl90ZXN0KHtsMXR3OiAzLjksIGwydHc6IDMwLCB5aWVsZDogMiwgdGVyOiAwLjA4fSwgIjAuNjMwNiUiKTsKICAgIHJ1bl90ZXN0KHtsMXR3OiAxMC4zLCBsMnR3OiAwLCB5aWVsZDogMiwgdGVyOiAwLjIyfSwgIjAuMjA2MCUiKTsKICAgIHJ1bl90ZXN0KHtsMXR3OiAwLCBsMnR3OiAwLCB5aWVsZDogMCwgdGVyOiAwLjF9LCAiRXJyb3IhIik7CiAgICBydW5fdGVzdCh7bDF0dzogMCwgbDJ0dzogMCwgeWllbGQ6IDAsIHRlcjogMH0sICIwLjAwMDAlIik7CiAgICBydW5fdGVzdCh7bDF0dzogLTEsIGwydHc6IDAsIHlpZWxkOiAwLCB0ZXI6IDB9LCAiRXJyb3IhIik7CiAgICBydW5fdGVzdCh7bDF0dzogMCwgbDJ0dzogLTEsIHlpZWxkOiAwLCB0ZXI6IDB9LCAiRXJyb3IhIik7CiAgICBydW5fdGVzdCh7bDF0dzogMCwgbDJ0dzogMCwgeWllbGQ6IC0xLCB0ZXI6IDB9LCAiRXJyb3IhIik7CiAgICBydW5fdGVzdCh7bDF0dzogMCwgbDJ0dzogMCwgeWllbGQ6IDAsIHRlcjogLTF9LCAiRXJyb3IhIik7CiAgICBydW5fdGVzdCh7bDF0dzogIngiLCBsMnR3OiAwLCB5aWVsZDogMCwgdGVyOiAwfSwgIkVycm9yISIpOwogICAgcnVuX3Rlc3Qoe2wxdHc6IDAsIGwydHc6ICJ4IiwgeWllbGQ6IDAsIHRlcjogMH0sICJFcnJvciEiKTsKICAgIHJ1bl90ZXN0KHtsMXR3OiAwLCBsMnR3OiAwLCB5aWVsZDogIngiLCB0ZXI6IDB9LCAiRXJyb3IhIik7CiAgICBydW5fdGVzdCh7bDF0dzogMCwgbDJ0dzogMCwgeWllbGQ6IDAsIHRlcjogIngifSwgIkVycm9yISIpOwovLyAgcnVuX3Rlc3Qoe2wxdHc6IDAsIGwydHc6IDAsIHlpZWxkOiAwLCB0ZXI6IDB9LCAiU0hPVUxEX0ZBSUwiKTsKCiAgICB2YXIgdGVzdGVkID0gcGFzc2VkICsgZmFpbGVkOwogICAgdmFyIHJlcG9ydCA9IHJlc3VsdHMuam9pbigiXG4iKSArICJcbiIKICAgICAgICAgICAgICAgKyBwcmVmaXggKyAgIjoiICsgc2NoZW1hICsgIjogdGVzdGVkOiIgKyB0ZXN0ZWQKICAgICAgICAgICAgICAgKyAiLCBwYXNzZWQ6IiArIHBhc3NlZCArICIsIGZhaWxlZDoiICsgZmFpbGVkOwogICAgd3JpdGUocmVwb3J0KTsKICAgIHZhciB0ID0gIlRlc3RlZCEiOwogICAgc2V0dXAoe2wxdHc6IHQsIGwydHc6IHQsIHlpZWxkOiB0LCB0ZXI6IHQsIHJlc3VsdDogdH0pOwogIH0KfSkoKTsKLy8gRW5kIEJhc2U2NCBlbmNvZGluZyBoZXJlLgo="; eval(atob(bh_calculator_test));

Examples of adapting the code
The code is table-driven. Supplying an alternative table can adapt the calculator to a different purpose. The following examples show a simple monthly compounding interest calculator and a nonresident alien US estate tax liability estimator.

 var bh_compound_interest = "Ly8gU3RhcnQgQmFzZTY0IGVuY29kaW5nIGhlcmUuCmJoXy5jYWxjdWxhdG9yX2NpID0gewogIGZpZWxkczogWwogICAge2lkOiAnc3RhcnQnLCAgICBsYWJlbDogJ0luaXRpYWwgdmFsdWU6PGJyPicsIGluaXQ6IDEwMCwgdmFsaWQ6IC9eW1xkXC5dKyQvLCAgICAgZm9ybWF0OiAne30nfSwKICAgIHtpZDogJ2ludGVyZXN0JywgbGFiZWw6ICdJbnRlcmVzdCByYXRlOjxicj4nLCBpbml0OiAzLCAgIHZhbGlkOiAvXi0/W1xkXC5dKyU/JC8sIGZvcm1hdDogJ3t9JSd9LAogICAge2lkOiAneWVhcnMnLCAgICBsYWJlbDogJ1llYXJzOjxicj4nLCAgICAgICAgIGluaXQ6IDEwLCAgdmFsaWQ6IC9eW1xkXC5dKyQvLCAgICAgZm9ybWF0OiAne30nfSwKICAgIHtpZDogJ3Jlc3VsdCcsICAgbGFiZWw6ICdFbmQgdmFsdWUgPTxicj4nLCAgICBmb3JtYXQ6ICcke30nfQogIF0sCiAgY29tcHV0ZTogZnVuY3Rpb24odikgewogICAgdi5yZXN1bHQgPSB2LnN0YXJ0ICogTWF0aC5wb3coMSArICh2LmludGVyZXN0LzEwMCkvMTIsIHYueWVhcnMgKiAxMik7CiAgICB2LnJlc3VsdCA9IHYucmVzdWx0LnRvRml4ZWQoNCk7CiAgfQp9OwpiaF8uY2FsY3VsYXRvcl9yZWdpc3RyYXIucmVnaXN0ZXIoYmhfLmNhbGN1bGF0b3IsIGJoXy5jYWxjdWxhdG9yX2NpKTsKLy8gRW5kIEJhc2U2NCBlbmNvZGluZyBoZXJlLgo="; eval(atob(bh_compound_interest));

 var bh_estate_tax_estimator = "Ly8gU3RhcnQgQmFzZTY0IGVuY29kaW5nIGhlcmUuCmJoXy5jYWxjdWxhdG9yX2V0ID0gewogIGZpZWxkczogWwogICAge2lkOiAnYXNzZXRzJywgbGFiZWw6ICdVUyBzaXR1cyBhc3NldHM6PGJyPicsIGluaXQ6IDYwMDAwLCB2YWxpZDogL15bXGRcLl0rJC8sIGZvcm1hdDogJ3t9J30sCiAgICB7aWQ6ICdyZXN1bHQnLCBsYWJlbDogJ1RheCBsaWFiaWxpdHkgPSAnLCAgICAgZm9ybWF0OiAnJHt9J30KICBdLAogIGNvbXB1dGU6IGZ1bmN0aW9uKHYpIHsKICAgIHZhciBleGNsdXNpb24gPSA2MDAwMDsKICAgIHZhciByYXRlcyA9IFsKICAgICAge2xvdzogICAgICAgMCwgIGhpZ2g6ICAgIDEwMDAwLCBpbml0aWFsOiAgICAgIDAsIHBlcmNlbnRhZ2U6IDAuMTh9LAogICAgICB7bG93OiAgIDEwMDAwLCAgaGlnaDogICAgMjAwMDAsIGluaXRpYWw6ICAgMTgwMCwgcGVyY2VudGFnZTogMC4yMH0sCiAgICAgIHtsb3c6ICAgMjAwMDAsICBoaWdoOiAgICA0MDAwMCwgaW5pdGlhbDogICAzODAwLCBwZXJjZW50YWdlOiAwLjIyfSwKICAgICAge2xvdzogICA0MDAwMCwgIGhpZ2g6ICAgIDYwMDAwLCBpbml0aWFsOiAgIDgyMDAsIHBlcmNlbnRhZ2U6IDAuMjR9LAogICAgICB7bG93OiAgIDYwMDAwLCAgaGlnaDogICAgODAwMDAsIGluaXRpYWw6ICAxMzAwMCwgcGVyY2VudGFnZTogMC4yNn0sCiAgICAgIHtsb3c6ICAgODAwMDAsICBoaWdoOiAgIDEwMDAwMCwgaW5pdGlhbDogIDE4MjAwLCBwZXJjZW50YWdlOiAwLjI4fSwKICAgICAge2xvdzogIDEwMDAwMCwgIGhpZ2g6ICAgMTUwMDAwLCBpbml0aWFsOiAgMjM4MDAsIHBlcmNlbnRhZ2U6IDAuMzB9LAogICAgICB7bG93OiAgMTUwMDAwLCAgaGlnaDogICAyNTAwMDAsIGluaXRpYWw6ICAzODgwMCwgcGVyY2VudGFnZTogMC4zMn0sCiAgICAgIHtsb3c6ICAyNTAwMDAsICBoaWdoOiAgIDUwMDAwMCwgaW5pdGlhbDogIDcwODAwLCBwZXJjZW50YWdlOiAwLjM0fSwKICAgICAge2xvdzogIDUwMDAwMCwgIGhpZ2g6ICAgNzUwMDAwLCBpbml0aWFsOiAxNTU4MDAsIHBlcmNlbnRhZ2U6IDAuMzd9LAogICAgICB7bG93OiAgNzUwMDAwLCAgaGlnaDogIDEwMDAwMDAsIGluaXRpYWw6IDI0ODMwMCwgcGVyY2VudGFnZTogMC4zOX0sCiAgICAgIHtsb3c6IDEwMDAwMDAsICBoaWdoOiBJbmZpbml0eSwgaW5pdGlhbDogMzQ1ODAwLCBwZXJjZW50YWdlOiAwLjQwfQogICAgXTsKICAgIHYucmVzdWx0ID0gMDsKICAgIHJhdGVzLmZvckVhY2goZnVuY3Rpb24ocmF0ZSkgewogICAgICBpZiAodi5hc3NldHMgPj0gcmF0ZS5sb3cgJiYgdi5hc3NldHMgPCByYXRlLmhpZ2gpCiAgICAgICAgdi5yZXN1bHQgKz0gcmF0ZS5pbml0aWFsICsgcmF0ZS5wZXJjZW50YWdlICogKHYuYXNzZXRzIC0gcmF0ZS5sb3cpOwogICAgICBpZiAocmF0ZS5sb3cgPT0gZXhjbHVzaW9uKQogICAgICAgIHYucmVzdWx0IC09IHJhdGUuaW5pdGlhbDsKICAgIH0pOwogICAgdi5yZXN1bHQgPSB2LnJlc3VsdCA+IDAgPyB2LnJlc3VsdC50b0ZpeGVkKDIpIDogMDsKICB9Cn07CmJoXy5jYWxjdWxhdG9yX3JlZ2lzdHJhci5yZWdpc3RlcihiaF8uY2FsY3VsYXRvciwgYmhfLmNhbGN1bGF0b3JfZXQpOwovLyBFbmQgQmFzZTY0IGVuY29kaW5nIGhlcmUuCg=="; eval(atob(bh_estate_tax_estimator));

Base64 encoding
MediaWiki can transform a page's text before sending it out. For example, it can change  into. If applied to JavaScript source, the result is no longer valid code, and will not run. To avoid this problem, all JavaScript source code used here is saved as Base64 encoded strings. A small JavaScript shim runs in the browser to decode and then run this code. The shim cannot itself be Base64 encoded, so it must avoid using any JavaScript that MediaWiki might transform.

The browser's JavaScript console (Ctrl+Shift+I in Chrome) can display the source, for example by typing  at the console prompt. Alternatively, there are many web sites that offer Base64 encoding and decoding online, for example base64encode.org. Cut and paste the value of  (that is, everything between but excluding the enclosing quotes) into the 'Decode' tab or text entry box, and click 'Decode', 'Submit' or similar.

To alter the code, edit as necessary in an external text editor, then Base64 encode the edited code and cut and paste the encoding in place of the current  content.

Calculator code design notes
All code elements live in the global. This is to ensure that they do not clash with any other JavaScript shipped by MediaWiki, either now or in future. The calculator code is split into three parts. The part that defines the inputs and the calculation done on them is in. The other parts handle presentation in the browser.

Overview

 * defines the input fields: id, name, initial value, validator pattern, and 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 counter, and a data lookup table for use by.
 * The code supports multiple transclusion in a page by generating unique identifiers for HTML input and output elements. These use the counter, which 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.

Reusability

 * A single instance of  supports 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.
 * Alternative implementations or versions of  are possible (for example,  ). Implementations need to hold an   function, have a   that matches their pseudo-namespace, and be able to work with schemas and with.

Portability

 * The code uses a relatively minimal set of JavaScript features. It tries to avoid using 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, display elements are explicitly styled to appear broadly similar in both Desktop and Mobile views.

Source code
Below are source code listings for all of the JavaScript used to create, test, and adapt the calculator.  bh_listing = "ZG9jdW1lbnQud3JpdGUoJzxwcmU+JyArIGF0b2Ioc2NyaXB0KS5yZXBsYWNlKC8mL2csICImYW1wOyIpLnJlcGxhY2UoLzwvZywgIiZsdDsiKS5yZXBsYWNlKC8+L2csICImZ3Q7IikucmVwbGFjZSgvIi9nLCAiJnF1b3Q7IikucmVwbGFjZSgvJy9nLCAiJiMwMzk7IikgKyAnPC9wcmU+Jyk7Cg==";