title: javascript OOP not working: Uncaught TypeError: object is not a function
body: <p>Ok, so backstory is we have a bunch of identical (in function) forms on a page to add variants to a product,
  The form consists of three major parts.</p>
  
  <h1>The Attribute Editor</h1>
  
  <p><img src="https://i.stack.imgur.com/jfmsB.png" alt="enter image description here"><br>
  This component allows the user to add attributes to a product.
  Each attribute has a <code>visibility status</code>, an <code>attribute key</code>, an <code>attribute value</code> and a <code>delete button</code> which together form one row.  </p>
  
  <p>The component also has an <code>Add Attribute</code> button, which when clicked adds a new row to the bottom of the list.</p>
  
  <p>Each <code>attribute key</code> select list has a <code>new attribute</code> option, which upon selecting launches a modal dialog with a form to enter the new attribute name, this form then submits via AJAX and returns an ID, the new option is then appended to every <code>attribute key</code> select on the page to allow it to be selected.</p>
  
  <p>When a key is selected in an instance of the component all other <code>attribute key</code> select's in the group get that option disabled to prevent duplicate attributes.</p>
  
  <p>The attribute editor gets submitted as part of the main form below.
  <hr /></p>
  
  <h1>The Main Form</h1>
  
  <p><img src="https://i.stack.imgur.com/SVtuw.png" alt="enter image description here"><br>
  This component consists of the general description fields of the variant.
  The form is submitted via AJAX and has the <a href="https://github.com/posabsolute/jQuery-Validation-Engine" rel="nofollow noreferrer">jQuery Validation Engine</a> attached for form validation.</p>
  
  <p>Because we are adding new input's dynamically with the attribute editor we must constantly detach, and re-attach the validation engine.</p>
  
  <hr />
  
  <h1>The Alert System</h1>
  
  <p><img src="https://i.stack.imgur.com/jDFJC.png" alt="enter image description here"><br>
  This component handles displaying/hiding the error/success/status messages on form by form basis.</p>
  
  <p><hr /></p>
  
  <h1>The Issue</h1>
  
  <p>Now there are also a couple of forms that are very similar, but are slight variantions on a couple of the event handlers, so I wanted to create the code so I could replace bits and pieces of it at will without having to copy the entire code.</p>
  
  <p>So after following the tips from <a href="https://stackoverflow.com/questions/13223618/new-instance-of-object-acting-as-reference/13224068">this question</a> I ended up with the code below, but I am getting the error: <code>Uncaught TypeError: object is not a function</code> which is on this line: <code>var variantAlert = new VariantAlert(form);</code> which I believe is because I am not returning anything, but I don't know what I should return to get the code to do what I want!</p>
  
  <p><strong>Short Version</strong></p>
  
  <pre><code>$(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);
      }
  
  
  })();
  </code></pre>
  
  <p><strong>Full Version</strong></p>
  
  <pre><code>$(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' &amp;&amp; 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's 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' &amp;&amp; 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($('&lt;option&gt;', { 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);
          });
      }
  
  
  })();
  </code></pre>