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.
This component allows the user to add attributes to a product.
Each attribute has a visibility status
, an attribute key
, an attribute value
and a delete button
which together form one row.
The component also has an Add Attribute
button, which when clicked adds a new row to the bottom of the list.
Each attribute key
select list has a new attribute
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 attribute key
select on the page to allow it to be selected.
When a key is selected in an instance of the component all other attribute key
select's in the group get that option disabled to prevent duplicate attributes.
The attribute editor gets submitted as part of the main form below.
This component consists of the general description fields of the variant.
The form is submitted via AJAX and has the jQuery Validation Engine attached for form validation.
Because we are adding new input's dynamically with the attribute editor we must constantly detach, and re-attach the validation engine.
This component handles displaying/hiding the error/success/status messages on form by form basis.
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.
So after following the tips from this question I ended up with the code below, but I am getting the error: Uncaught TypeError: object is not a function
which is on this line: var variantAlert = new VariantAlert(form);
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!
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'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' && 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);
});
}
})();
Yours variantAlert uses like
variantAlert.message('failed');
That means constructor must return object containing message function
var VariantAlert = function (form) {
var timer;
/*********************************
* 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);
});
}
return {
message: message
};
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With