Well, so the background - we have a bunch of identical (in function) forms on the page to add options to the product. The form consists of three main parts.
Attribute Editor

This component allows the user to add attributes to the product. Each attribute has visibility status , attribute key , a attribute value and a delete button , which together form one line.
The component also has an Add Attribute button that, when clicked, adds a new line to the bottom of the list.
Each attribute key selection list has a new attribute parameter, which, after selection, launches a modal dialog box with a form for entering a new attribute name, this form is then sent via AJAX and returns an identifier, then a new parameter is added for each attribute key select on the page so that it can to be selected.
When a key is selected in an instance of a component, all other attribute key are selected in the group so that this parameter is disabled to prevent duplication of attributes.
The attribute editor is submitted as part of the main form below.
Basic form

This component consists of general variant description fields. The form is submitted via AJAX and has a jQuery validation mechanism attached to validate the form.
Since we add the new input dynamically using the attribute editor, we must constantly disconnect and reattach the verification mechanism.
Alert system

This component handles showing / hiding error / success / status messages in the form on the form.
Problem
Now there are also several forms that are very similar, but are small options for a couple of event handlers, so I wanted to create the code so that I could replace its pieces as I wanted without having to copy all the code.
So, after the following tips from this question , I ended up with the code below, but I get the error: Uncaught TypeError: object is not a function , which is in this line: var variantAlert = new VariantAlert(form); which, I believe, is because I am not returning anything, but I do not know what I have to return in order to get the code to do what I want!
Short version
$(function () { $("form.variant-form").each(function (i, form) { var variantAlert = new VariantAlert(form); var variantForm = new VariantForm(form, variantAlert); variantForm.init(); var attributeEditor = new AttributeEditor(form, variantForm); attributeEditor.init(); }); }); var AttributeEditor = (function (form, formSetup) { form = $('form'); var someVar = 123; var init = function () { someEventHandler(); }; var someEventHandler = function () { $('.selector', form).on('some event', function (e) { form.css('background-color', '#f00'); }); }; return AttributeEditor; })(); var VariantForm = (function (form, variantAlert) { form = $('form'); var init = function () { anotherEventHandler(); }; var anotherEventHandler = function () { $('.anotherSelector', form).on('another event', function () { form.doStuff(); }); }; })(); var VariantAlert = (function (form) { var timer; form = $('form'); var message = function (type, message) { doMoreStuff(type, message); } })();
Full version
$(function () { /********************************* * Loop over each variant and setup * the attribute editor and form *********************************/ $("form.variant-form").each(function (i, form) { var variantAlert = new VariantAlert(form); var variantForm = new VariantForm(form, variantAlert); variantForm.init(); var attributeEditor = new AttributeEditor(form, variantForm); attributeEditor.init(); }); }); var AttributeEditor = (function (form, formSetup) { /********************************* * Variables *********************************/ form = $('form'); var template = $('.variant_demo_row', form); var attributes = $('.variant_select', form).length; var modal = form.siblings('.newAttribute').appendTo('body'); var manualHide = false; var triggerSelect = null; var oldOption = null; var init = function () { //setup the handlers //doing it this way allows us to overwrite the individual handlers with ease addNewAttributeHandler(); removeAttributeHandler(); selectFocusHandler(); selectChangeHandler(); attributeVisibilityHandler(); modalFormSubmissionHandler(); modalShowHandler(); modalCancelClickHandler(); }; /********************************* * Add new attribute button handler *********************************/ var addNewAttributeHandler = function () { $('.variant_attribute_add_new a', form).on('click keypress', function (e) { form.css('background-color', '#f00'); //patched support for enter key if (e.type === 'keypress' && e.which != 13) { return true; } //clone the template row so we can edit it var newRow = template.clone().css('display', 'none').removeClass('hidden variant_demo_row').addClass('variant_row'); //give each element in the clone it unique name $('.variant_select', newRow).prop('name', 'attribute_key_' + attributes); $('.variant_input', newRow).prop('name', 'attribute_value_' + attributes); $('.variant_visible', newRow).prop('name', 'attribute_visible_' + attributes); //insert the new attribute row at the bottom of the attributes newRow.insertBefore($('.variant_attribute_add_new', form)).show('fast', function () { $('select', newRow).focus(); }); //we have added new nodes so we need to reset the validationEngine form.validationEngine('detach'); formSetup.init(); attributes++; }); }; /********************************* * Remove attribute button handler *********************************/ var removeAttributeHandler = function () { form.on('click keypress', '.removeAttribute', {}, function (e) { //patched support for enter key if (e.type === 'keypress' && e.which != 13) { return true; } attributes--; var val = $(this).siblings('select').val(); //re-enable whatever attribute key was in use if (val != "") { $('.variant_select option[value=' + val + ']', form).removeAttr('disabled'); } //animate the removal of the attribute $(this).closest('.controls-row').hide('fast', function () { $(this).remove(); }); }); }; /********************************* * Attribute key select focus handler *********************************/ var selectFocusHandler = function () { form.on('focus', '.variant_select', {}, function () { //store the old option so we know what option to //re-enable if a change is made oldOption = $('option:selected', this).val(); }); }; /********************************* * Attribute key select change handler *********************************/ var selectChangeHandler = function () { form.on('change', '.variant_select', {}, function () { var select = $(this); //empty class is used for "placeholder" simulation select.removeClass('empty'); //re-enable whatever option was previously selected if (oldOption !== null) { $('.variant_select option[value=' + oldOption + ']', form).removeAttr('disabled'); } if ($('option:selected', select).hasClass('newAttribute')) { //Add new attribute selected triggerSelect = select; modal.modal('show'); } else if ($('option:selected', select).val() == "") { //Placeholder selected select.addClass('empty'); } else { //Value selected //disable the selected value in other attribute key selects $('.variant_select', form).not(select).children('option[value=' + select.val() + ']').prop('disabled', 'disabled'); } oldOption = select.val(); }); }; /********************************* * Toggle visibility button handler *********************************/ var attributeVisibilityHandler = function () { form.on('click', '.toggleVisibility', {}, function () { //the titles of the button var hidden = 'Hidden Attribute'; var visible = 'Visible Attribute'; var btn = $(this); var icon = btn.children('i'); var box = btn.siblings('.variant_visible'); //toggle the state between visible and hidden btn.toggleClass('btn-success btn-warning').attr('title', btn.attr('title') == hidden ? visible : hidden); icon.toggleClass('icon-eye-open icon-eye-close'); box.prop("checked", !box.prop("checked")) }); }; /********************************* * New attribute submission handler *********************************/ var modalFormSubmissionHandler = function () { $('.newAttributeForm', modal).validationEngine('attach', { onValidationComplete:function (form, status) { if (status) { var text = $('.newAttributeName', modal).val(); $('.newAttributeName', modal).val(''); form.spin(); $.ajax({ type:'POST', url:'/cfox/cart/variants/addattribute', data:{name:text}, success:function (data) { //add new attribute key to attribute key selects everywhere $('.variant_select').append($('<option>', { value:data.id}).text(data.name)); //set the triggering selects value to the new key triggerSelect.val(data.id); triggerSelect.trigger('change'); manualHide = true; modal.modal('hide'); triggerSelect.siblings('input').focus(); form.spin(false); }, dataType:'JSON' }); } }}); }; var modalCancelClickHandler = function () { $('.btn-danger', modal).on('click', function () { if (!manualHide) { triggerSelect[0].selectedIndex = 1; triggerSelect.trigger('change'); } manualHide = false; }); }; var modalShowHandler = function () { modal.on('show shown', function () { $('.newAttributeName', modal).focus(); }); } return AttributeEditor; })(); var VariantForm = (function (form, variantAlert) { /********************************* * Variables *********************************/ form = $('form'); var init = function () { nameChangeHandler(); submitHandler(); }; /********************************* * Variant name change handler * Changes the heading on the accordion if the * name form input changes *********************************/ var nameChangeHandler = function () { var accordion_heading = form.closest('.accordion-body').siblings('.accordion-heading').find('.accordion-toggle'); $('.name-input', form).on('change', function () { accordion_heading.text($(this).val()); }); }; /********************************* * Form submit handler *********************************/ var submitHandler = function () { form.validationEngine('attach', { onValidationComplete:function (form, status) { if (status == true) { $.ajax({ type:'POST', url:form.attr('action'), data:form.serialize(), dataType:'json', beforeSend:function () { cfox.disableForm(form); form.spin(); form.children('.variant_status_message').hide('fast'); }, success:function (response) { cfox.enableForm(form);//need to do this here so browser doesn't cache disabled fields if (typeof response != "object" || response === null) { variantAlert.message('failed'); } else { switch (response.status) { case 0: variantAlert.message('errors', response.errors); break; case 1: variantAlert.message('success'); break; default: variantAlert.message('failed'); break; } } form.spin(false); }, error:function () { variantAlert.message('failed'); form.spin(false); cfox.enableForm(form); } }); } } }); } })(); var VariantAlert = (function (form) { /********************************* * Variables *********************************/ var timer; form = $('form'); /********************************* * handles showing/hiding any messages * in the variant forms *********************************/ var message = function (type, message) { var alert; clearTimeout(timer); $('.variant_status_message', form).hide('fast'); if (type == 'success') { alert = $('.variant_status_message.success', form); } else if (type == 'errors') { alert = $('.variant_status_message.errors', form); $('.alert-message', alert).html(message); } else if (type == 'failed') { alert = $('.variant_status_message.failed', form); } alert.show('fast', function () { $('html, body').animate({ scrollTop:alert.closest('.accordion-group').offset().top }, 150, 'linear'); timer = setTimeout(function () { alert.hide('fast') }, 5000); }); } })();