MediaWiki:Gadget-calc-core.js: Difference between revisions

Created page with "global mediaWiki, mw, gswiki, gs, OO: 'use strict'; /** * Prefix of localStorage key for calc data. This is prepended to the form ID * localStorage name fo..."
 
No edit summary
 
Line 3: Line 3:
'use strict';
'use strict';


    /**
    * Prefix of localStorage key for calc data. This is prepended to the form ID
    * localStorage name for autosubmit setting
    */
var calcstorage = 'gsw-calcsdata',
var calcstorage = 'gsw-calcsdata',
     calcautostorage = 'gsw-calcsdata-allautosub',
     calcautostorage = 'gsw-calcsdata-allautosub',
     /**
      
    * Caching for search suggestions
    *
    * @todo implement caching for mw.TitleInputWidget accroding to https://doc.wikimedia.org/mediawiki-core/master/js/#!/api/mw.widgets.TitleWidget-cfg-cache
    */
     cache = {},
     cache = {},


    /**
    * Internal variable to store references to each calculator on the page.
    */
     calcStore = {},
     calcStore = {},


    /**
    * Private helper methods for `Calc`
    *
    * Most methods here are called with `Function.prototype.call`
    * and are passed an instance of `Calc` to access it's prototype
    */
     helper = {
     helper = {
        /**
   
        * Add/change functionality of mw/OO.ui classes
        * Added support for multiple namespaces to mw.widgets.TitleInputWidget
        */
         initClasses: function () {
         initClasses: function () {
             var hasOwn = Object.prototype.hasOwnProperty;
             var hasOwn = Object.prototype.hasOwnProperty;
             /**
              
            * Get option widgets from the server response
            * Changed to add support for multiple namespaces
            *
            * @param {Object} data Query result
            * @return {OO.ui.OptionWidget[]} Menu items
            */
             mw.widgets.TitleInputWidget.prototype.getOptionsFromData = function (data) {
             mw.widgets.TitleInputWidget.prototype.getOptionsFromData = function (data) {
                 var i, len, index, pageExists, pageExistsExact, suggestionPage, page, redirect, redirects,
                 var i, len, index, pageExists, pageExistsExact, suggestionPage, page, redirect, redirects,
Line 932: Line 906:
                 return new OO.ui.FieldLayout(param.ooui, layconf);
                 return new OO.ui.FieldLayout(param.ooui, layconf);
             },
             },
            /**
            * Handler for button selects
            *
            * @param param {object} An object containing the configuration of a parameter
            * @param id {String} A string representing the id to be added to the input
            * @returns {OOUI.object} A OOUI object containing the new FieldLayout
            */
            buttonselect: function (param, id) {
                var self = this,
                    buttons = {},
                    conf = {
                        label:'Select an option',
                        items: [],
                        id: id
                    },
                    layconf = {
                        label: new OO.ui.HtmlSnippet(param.label),
                        align: 'right',
                        classes: ['jsCalc-field', 'jsCalc-field-buttonselect']
                    },
                    opts = param.range.split(/\s*,\s*/),
                    def;
                param.error = 'Please select a valid option';
                if (param.help) {
                    layconf.helpInline = param.inlhelp;
                    layconf.help = new OO.ui.HtmlSnippet(param.help);
                }
                opts.forEach(function (opt) {
                    var opid = opt.replace(/[^a-zA-Z0-9]/g, '');
                    buttons[opid] = new OO.ui.ButtonOptionWidget({data:opt, label:opt, title:opt});
                    conf.items.push(buttons[opid]);
                });
                if (param.def.length > 0 && opts.indexOf(param.def) > -1) {
                    def = param.def;
                } else {
                    def = opts[0];
                }
                param.toggles = helper.parseToggles(param.rawtogs, def);
                param.ooui = new OO.ui.ButtonSelectWidget(conf);
                param.ooui.selectItemByData(def);
                if ( Object.keys(param.toggles).length > 0 ) {
                    param.ooui.on('choose', function (button) {
                        var item = button.getData();
                        helper.toggle.call(self, item, param.toggles);
                    });
                }
                return new OO.ui.FieldLayout(param.ooui, layconf);
            },
            /**
            * Handler for comboboxes
            *
            * @param param {object} An object containing the configuration of a parameter
            * @param id {String} A string representing the id to be added to the input
            * @returns {OOUI.object} A OOUI object containing the new FieldLayout
            */
            combobox: function (param, id) {
                var self = this,
                    conf = {
                        placeholder: 'Enter filter name',
                        options: [],
                        name: id,
                        id: id,
                        menu: { filterFromInput: true },
                        value: param.def
                    },
                    layconf = {
                        label: new OO.ui.HtmlSnippet(param.label),
                        align: 'right',
                        classes: ['jsCalc-field', 'jsCalc-field-combobox']
                    },
                    opts = param.range.split(/\s*,\s*/),
                    def = opts[0];
                param.error = 'Not a valid selection';
                if (param.help) {
                    layconf.helpInline = param.inlhelp;
                    layconf.help = new OO.ui.HtmlSnippet(param.help);
                }
                opts.forEach(function (opt) {
                    var op = { data: opt, label: opt };
                    if (opt === param.def) {
                        op.selected = true;
                        def = opt;
                    }
                    conf.options.push(op);
                });
                var isvalid = function (val) {return opts.indexOf(val) < 0 ? false : true;};
                conf.validate = isvalid;
                param.toggles = helper.parseToggles(param.rawtogs, def);
param.ooui = new OO.ui.ComboBoxInputWidget(conf);
                if ( Object.keys(param.toggles).length > 0 ) {
                    param.ooui.on('change', function (value) {
                        helper.toggle.call(self, value, param.toggles);
                    });
                }
                return new OO.ui.FieldLayout(param.ooui, layconf);
            },
            /**
            * Handler for checkbox inputs
            *
            * @param param {object} An object containing the configuration of a parameter
            * @param id {String} A string representing the id to be added to the input
            * @returns {OOUI.object} A OOUI object containing the new FieldLayout
            */
            check: function (param, id) {
                var self = this,
                    conf = {
                        name: id,
                        id: id
                    },
                    layconf = {
                        label: new OO.ui.HtmlSnippet(param.label),
                        align: 'right',
                        classes: ['jsCalc-field', 'jsCalc-field-check']
                    };
                param.toggles = helper.parseToggles(param.rawtogs, 'true');
                param.error = 'Unknown error';
                if (param.help) {
                    layconf.helpInline = param.inlhelp;
                    layconf.help = new OO.ui.HtmlSnippet(param.help);
                }
                if ( param.def === 'true' ||
                    (param.range !== undefined && param.def === param.range.split(/\s*,\s*/)[0]) ) {
                    conf.selected = true;
                }
param.ooui = new OO.ui.CheckboxInputWidget(conf);
                if ( Object.keys(param.toggles).length > 0 ) {
                    param.ooui.on('change', function (selected) {
                        if (selected) {
                            helper.toggle.call(self, 'true', param.toggles);
                        } else {
                            helper.toggle.call(self, 'false', param.toggles);
                        }
                    });
                }
                return new OO.ui.FieldLayout(param.ooui, layconf);
            },
            /**
            * Handler for toggle switch inputs
            *
            * @param param {object} An object containing the configuration of a parameter
            * @param id {String} A string representing the id to be added to the input
            * @returns {OOUI.object} A OOUI object containing the new FieldLayout
            */
            toggleswitch: function (param, id) {
                var self = this,
                    conf = { id: id },
                    layconf = {
                        label: new OO.ui.HtmlSnippet(param.label),
                        align: 'right',
                        classes: ['jsCalc-field', 'jsCalc-field-toggleswitch']
                    };
                param.toggles = helper.parseToggles(param.rawtogs, 'true');
                param.error = 'Unknown error';
                if (param.help) {
                    layconf.helpInline = param.inlhelp;
                    layconf.help = new OO.ui.HtmlSnippet(param.help);
                }
                if ( param.def === 'true' ||
                    (param.range !== undefined && param.def === param.range.split(/\s*,\s*/)[0]) ) {
                    conf.value = true;
                }
                param.ooui = new OO.ui.ToggleSwitchWidget(conf);
                if ( Object.keys(param.toggles).length > 0 ) {
                    param.ooui.on('change', function (selected) {
                        if (selected) {
                            helper.toggle.call(self, 'true', param.toggles);
                        } else {
                            helper.toggle.call(self, 'false', param.toggles);
                        }
                    });
                }
                return new OO.ui.FieldLayout(param.ooui, layconf);
            },
            /**
            * Handler for toggle button inputs
            *
            * @param param {object} An object containing the configuration of a parameter
            * @param id {String} A string representing the id to be added to the input
            * @returns {OOUI.object} A OOUI object containing the new FieldLayout
            */
            togglebutton: function (param, id) {
                var self = this,
                    conf = {
                        id: id,
                        label: new OO.ui.HtmlSnippet(param.label)
                    },
                    layconf = {
                        label:'',
                        align: 'right',
                        classes: ['jsCalc-field', 'jsCalc-field-togglebutton']
                    };
                param.toggles = helper.parseToggles(param.rawtogs, 'true');
                param.error = 'Unknown error';
                if (param.help) {
                    layconf.helpInline = param.inlhelp;
                    layconf.help = new OO.ui.HtmlSnippet(param.help);
                }
                if ( param.def === 'true' ||
                    (param.range !== undefined && param.def === param.range.split(/\s*,\s*/)[0]) ) {
                    conf.value = true;
                }
                param.ooui = new OO.ui.ToggleButtonWidget(conf);
                if ( Object.keys(param.toggles).length > 0 ) {
                    param.ooui.on('change', function (selected) {
                        if (selected) {
                            helper.toggle.call(self, 'true', param.toggles);
                        } else {
                            helper.toggle.call(self, 'false', param.toggles);
                        }
                    });
                }
                return new OO.ui.FieldLayout(param.ooui, layconf);
            },
            /**
            * Handler for integer inputs
            *
            * @param param {object} An object containing the configuration of a parameter
            * @param id {String} A string representing the id to be added to the input
            * @returns {OOUI.object} A OOUI object containing the new FieldLayout
            */
            int: function (param, id) {
                var self = this,
                    rng = helper.genRange(param.range, 'int'),
                    conf = {
                        min:rng[0],
                        max:rng[1],
                        step:rng[2],
                        showButtons:true,
                        buttonStep:rng[3],
                        allowInteger:true,
                        name: id,
                        id: id,
                        value: param.def || 0
                    },
                    layconf = {
                        label: new OO.ui.HtmlSnippet(param.label),
                        align: 'right',
                        classes: ['jsCalc-field', 'jsCalc-field-int']
                    },
                    error = 'Invalid integer. Must be between ' + rng[0] + ' and ' + rng[1];
                param.toggles = helper.parseToggles(param.rawtogs, 'not0');
                if (param.help) {
                    layconf.helpInline = param.inlhelp;
                    layconf.help = new OO.ui.HtmlSnippet(param.help);
                }
                if ( rng[2] > 1 ) {
                    error += ' and a muiltiple of ' + rng[2];
                }
                param.error = error;
param.ooui = new OO.ui.NumberInputWidget(conf);
                if ( Object.keys(param.toggles).length > 0 ) {
                    param.ooui.on('change', function (value) {
                        helper.toggle.call(self, value, param.toggles);
                    });
                }
                return new OO.ui.FieldLayout(param.ooui, layconf);
            },
           
            /**
            * Handler for number inputs
            *
            * @param param {object} An object containing the configuration of a parameter
            * @param id {String} A string representing the id to be added to the input
            * @returns {OOUI.object} A OOUI object containing the new FieldLayout
            */
            number: function (param, id) {
                var self = this,
                    rng = helper.genRange(param.range, 'number'),
                    conf = {
                        min:rng[0],
                        max:rng[1],
                        step:rng[2],
                        showButtons:true,
                        buttonStep:rng[3],
                        name:id,
                        id:id,
                        value:param.def || 0
                    },
                    layconf = {
                        label: new OO.ui.HtmlSnippet(param.label),
                        align: 'right',
                        classes: ['jsCalc-field', 'jsCalc-field-number'],
                    };
                param.toggles = helper.parseToggles(param.rawtogs, 'not0');
                param.error = 'Invalid interger. Must be between ' + rng[0] + ' and ' + rng[1] + ' and a multiple of ' + rng[2];
                if (param.help) {
                    layconf.helpInline = param.inlhelp;
                    layconf.help = new OO.ui.HtmlSnippet(param.help);
                }
                param.ooui = new OO.ui.NumberInputWidget(conf);
                if ( Object.keys(param.toggles).length > 0 ) {
                    param.ooui.on('change', function (value) {
                        helper.toggle.call(self, value, param.toggles);
                    });
                }
                return new OO.ui.FieldLayout( param.ooui, layconf);
            },
            /**
            * Handler for article inputs
            *
            * @param param {object} An object containing the configuration of a parameter
            * @param id {String} A string representing the id to be added to the input
            * @returns {OOUI.object} A OOUI object containing the new FieldLayout
            */
            article: function (param, id) {
                var self = this,
                    conf = {
                        addQueryInput: false,
                        excludeCurrentPage: true,
                        showMissing: false,
                        showDescriptions: true,
                        validateTitle: true,
                        relative: false,
                        id: id,
                        name: id,
                        placeholder: 'Enter page name',
                        value: param.def
                    },
                    layconf = {
                        label: new OO.ui.HtmlSnippet(param.label),
                        align:'right',
                        classes: ['jsCalc-field', 'jsCalc-field-article']
                    },
                    validNSnumbers = { '_*':'All', '_-2':'Media', '_-1':'Special', _0:'(Main)', _1:'Talk', _2:'User', _3:'User talk', _4:'RuneScape', _5:'RuneScape talk', _6:'File', _7:'File talk', _8:'MediaWiki', _9:'MediaWiki talk', _10:'Template', _11:'Template talk',
                        _12:'Help', _13:'Help talk', _14:'Category', _15:'Category talk', _100:'Update', _101:'Update talk', _110:'Forum', _111:'Forum talk', _112:'Exchange', _113:'Exchange talk', _114:'Charm', _115:'Charm talk', _116:'Calculator', _117:'Calculator talk', _118:'Map', _119:'Map talk', _828:'Module', _829:'Module talk' },
                    validNSnames = { all:'*', media:-2, special:-1, main:0, '(main)':0, talk:1, user:2, 'user talk':3, runescape:4, 'runescape talk':5, file:6, 'file talk':7, mediawiki:8, 'mediawiki talk':9, template:10, 'template talk':11,
                        help:12, 'help talk':13, category:14, 'category talk':15, update:100, 'update talk':101, forum:110, 'forum talk':111, exchange:112, 'exchange talk':113, charm:114, 'charm talk':115, calculator:116, 'calculator talk':117, map:118, 'map talk':119, module:828, 'module talk':829 },
                    namespaces = '';
                if (param.help) {
                    layconf.helpInline = param.inlhelp;
                    layconf.help = new OO.ui.HtmlSnippet(param.help);
                }
                if (param.range && param.range.length > 0) {
                    var names = param.range.split(/\s*,\s*/),
                    nsnumbers = [];
                    names.forEach( function (nmspace) {
                        nmspace = nmspace.toLowerCase();
                        if ( validNSnumbers['_'+nmspace] ) {
                            nsnumbers.push(nmspace);
                        } else if ( validNSnames[nmspace] ) {
                            nsnumbers.push( validNSnames[nmspace] );
                        }
                    });
                    if (nsnumbers.length < 1) {
                        conf.namespace = '0';
                        namespaces = '(Main) namespace';
                    } else if (nsnumbers.length < 2) {
                        conf.namespace = nsnumbers[0];
                        namespaces = nsnumbers[0] + ' namespace';
                    } else {
                        conf.namespace = nsnumbers.join('|');
                        var nsmap = function (num) {
                            return validNSnumbers['_'+num];
                        };
                        namespaces = nsnumbers.slice(0, -1).map(nsmap).join(', ') + ' or ' + nsnumbers.slice(-1).map(nsmap)[0] + ' namespaces';
                    }
                } else if ( self.suggestns && self.suggestns.length > 0 ) {
                    var nsnumbers = [];
                    self.suggestns.forEach( function (nmspace) {
                        nmspace = nmspace.toLowerCase();
                        if ( validNSnumbers['_'+nmspace] ) {
                            nsnumbers.push(nmspace);
                        } else if ( validNSnames[nmspace] ) {
                            nsnumbers.push( validNSnames[nmspace] );
                        }
                    });
                    if (nsnumbers.length < 1) {
                        conf.namespace = '0';
                        namespaces = '(Main) namespace';
                    } else if (nsnumbers.length < 2) {
                        conf.namespace = nsnumbers[0];
                        namespaces = nsnumbers[0] + ' namespace';
                    } else {
                        conf.namespace = nsnumbers.join('|');
                        var nsmap = function (num) {
                            return validNSnumbers['_'+num];
                        };
                        namespaces = nsnumbers.slice(0, -1).map(nsmap).join(', ') + ' or ' + nsnumbers.slice(-1).map(nsmap)[0] + ' namespaces';
                    }
                } else {
                    conf.namespace = '0';
                    namespaces = '(Main) namespace';
                }
                param.error = 'Invalid page or page is not in ' + namespaces;
                param.ooui = new mw.widgets.TitleInputWidget(conf);
                return new OO.ui.FieldLayout( param.ooui, layconf);
            },
            /**
            * Handler for group type params
            *
            * @param param {object} An object containing the configuration of a parameter
            * @param id {String} A string representing the id to be added to the input
            * @returns {OOUI.object} A OOUI object containing the new FieldLayout
            */
            group: function (param, id) {
                param.ooui = new OO.ui.HorizontalLayout({id: id, classes: ['jsCalc-group']});
                if (param.label !== param.name) {
                    var label = new OO.ui.LabelWidget({ label: new OO.ui.HtmlSnippet(param.label), classes:['jsCalc-grouplabel'] });
                    param.ooui.addItems([label]);
                }
                return param.ooui;
            },
           
            /**
            * Default handler for inputs
            *
            * @param param {object} An object containing the configuration of a parameter
            * @param id {String} A string representing the id to be added to the input
            * @returns {OOUI.object} A OOUI object containing the new FieldLayout
            */
            def: function (param, id) {
                var layconf = {
                    label: new OO.ui.HtmlSnippet(param.label),
                    align: 'right',
                    classes: ['jsCalc-field', 'jsCalc-field-string'],
                    value: param.def
                };
                param.error = 'Unknown error';
                if (param.help) {
                    layconf.helpInline = param.inlhelp;
                    layconf.help = new OO.ui.HtmlSnippet(param.help);
                }
param.ooui = new OO.ui.TextInputWidget({type: 'text', name: id, id: id});
                return new OO.ui.FieldLayout(param.ooui, layconf);
            }
        }
    };
/**
* Create an instance of `Calc`
* and parse the config stored in `elem`
*
* @param elem {Element} An Element representing the HTML tag that contains
*                      the calculator's configuration
*/
function Calc(elem) {
    var self = this,
        $elem = $(elem),
        lines,
        config;
       
    // support div tags for config as well as pre
    // be aware using div tags relies on wikitext for parsing
    // so you can't use anchor or img tags
    // use the wikitext equivalent instead
    if ($elem.children().length) {
        $elem = $elem.children();
        lines = $elem.html();
    } else {
        // .html() causes html characters to be escaped for some reason
        // so use .text() instead for <pre> tags
        lines = $elem.text();
    }
   
    lines = lines.split('\n');
   
    config = helper.parseConfig.call(this, lines);
    // Calc name for localstorage, keyed to calc id
    this.localname = calcstorage + '-' + config.form;
   
    // Load previous parameter values.
    if (!gs.hasLocalStorage()) {
        console.warn('Browser does not support localStorage');
    } else {
        console.log('Loading previous calculator values');
        if ( config.autosubmit !== 'disabled' ) {
            config.autosubmit = localStorage.getItem(calcautostorage);
        }
        var calcdata = JSON.parse( localStorage.getItem(this.localname) ) || false;
        if (calcdata) {
            config.tParams.forEach( function(param) {
                if (calcdata[param.name] !== undefined && calcdata[param.name] !== null) {
                    param.def = calcdata[param.name];
                }
            });
        }
        self.lsGSN = localStorage.getItem('gsn');
        console.log(config);
    }
    // merge config in
    $.extend(this, config);
    /**
    * @todo document
    */
    this.getInput = function (id) {
        if (id) {
            id = helper.getId.call(self, id);
            return $('#' + id);
        }
       
        return $('#jsForm-' + self.form).find('select, input');
    };
}
/**
* Helper function for getting the id of an input
*
* @param id {string} The id of the input as specified by the calculator config.
* @returns {string} The true id of the input with prefixes.
*/
Calc.prototype.getId = function (id) {
    var self = this,
        inputId = helper.getId.call(self, id);
    return inputId;
};
/**
* Build the calculator form
*/
Calc.prototype.setupCalc = function () {
    var self = this,
        fieldset = new OO.ui.FieldsetLayout({label: self.name, classes: ['jcTable'], id: 'jsForm-'+self.form}),
submitButton, submitButtonAction, autosubmit, paramChangeAction,
        groupkeys = {};
    // Used to store indexes of elements to toggle them later
    self.indexkeys = {};
    self.tParams.forEach(function (param, index) {
        // can skip any output here as the result is pulled from the
        // param default in the config on submission
        if (param.type === 'hidden') {
            return;
        }
        var id = helper.getId.call(self, param.name),
            method = helper.tParams[param.type] ?
                param.type :
                'def';
        // Generate list of items in group
        if (param.type === 'group') {
            var fields = param.range.split(/\s*,\s*/);
            fields.forEach( function (field) {
                groupkeys[field] = index;
            });
        }
        param.layout = helper.tParams[method].call(self, param, id);
        if (param.type === 'semihidden') {
            param.layout.toggle(false);
        }
        // Add to group or form
        if ( groupkeys[param.name] || groupkeys[param.name] === 0 ) {
            self.tParams[ groupkeys[param.name] ].ooui.addItems([param.layout]);
        } else {
            fieldset.addItems([param.layout]);
        }
        // Add item to indexkeys
        self.indexkeys[param.name] = index;
    });
    // Run toggle for each field, check validity
    self.tParams.forEach( function (param) {
        if (param.toggles && Object.keys(param.toggles).length > 0) {
            var val;
            if (param.type === 'buttonselect') {
                val = param.ooui.findSelectedItem().getData();
            } else if (param.type === 'check') {
                val = param.ooui.isSelected() ? 'true' : 'false';
            } else if (param.type === 'toggleswitch' || param.type === 'togglebutton') {
                val = param.ooui.getValue() ? 'true' : 'false';
            } else {
                val = param.ooui.getValue();
            }
            helper.toggle.call(self, val, param.toggles);
        }
        if (param.type === 'number' || param.type === 'int' || param.type === 'gsn') {
            param.ooui.setValidityFlag();
        }
    });
submitButton = new OO.ui.ButtonInputWidget({ label: 'Submit', flags: ['primary', 'progressive'], classes: ['jcSubmit']});
submitButtonAction = function (){
                helper.submitForm.call(self);
};
submitButton.on('click', submitButtonAction);
submitButton.$element.data('oouiButton', submitButton);
    self.submitlayout = new OO.ui.FieldLayout(submitButton, {label: ' ', align: 'right', classes: ['jsCalc-field', 'jsCalc-field-submit']});
fieldset.addItems([ self.submitlayout ]);
    // Auto-submit
    if (self.autosubmit !== 'disabled') {
        // Add toggle to fieldset
        autosubmit = new OO.ui.ToggleSwitchWidget({
            value: self.autosubmit === 'on' || self.autosubmit === 'true'
        });
        autosubmit.on('change', function (value) { self.autosubmit = value; });
        fieldset.addItems([ new OO.ui.FieldLayout(autosubmit, { label:'Auto-submit', align:'right', classes:['jsCalc-field', 'jsCalc-field-autosubmit'] }) ]);
        // Add event
        paramChangeAction = function (widget) {
            if (autosubmit.getValue()) {
                if ( typeof widget.getFlagsa === 'undefined' || !widget.getFlags().includes('invalid')) {
                    helper.submitForm.call(self);
                }
            }
        };
        self.tParams.forEach( function (param) {
            if (param.type === 'hidden' || param.type === 'hs' || param.type === 'group') {
                return;
            } else if (param.type === 'buttonselect') {
                param.ooui.on('select', setTimeout, [paramChangeAction, 500, param.ooui]);
            }
            param.ooui.on('change', setTimeout, [paramChangeAction, 500, param.ooui]);
        });
    }
   
    if (self.configError) {
        fieldset.$element.append('<br>', self.configError);
    }
    $('#bodyContent')
        .find('#' + self.form)
            .empty()
            .append(fieldset.$element);
};
/**
* @todo
*/
function lookupCalc(calcId) {
    return calcStore[calcId];
}
/**
* @todo
*/
function init() {
    // Initialises class changes
    helper.initClasses();
    $('.jcConfig').each(function () {
        var c = new Calc(this);
        c.setupCalc();
       
        calcStore[c.form] = c;
        if (c.autosubmit === 'true' || c.autosumit === true) {
            helper.submitForm.call(c);
        }
    });
   
    // allow scripts to hook into calc setup completion
    mw.hook('rscalc.setupComplete').fire();
}
$(init);
gs.calc = {};
gs.calc.lookup = lookupCalc;
// </nowiki>