javascript OOP不工作:未捕获TypeError:对象不是函数

时间:2012-11-05 02:41:02

标签: javascript jquery oop function

好的,所以背景故事是我们在页面上有一堆相同的(函数内)表单来为产品添加变体, 表格由三个主要部分组成。

属性编辑器

enter image description here
该组件允许用户向产品添加属性。 每个属性都有一个visibility status,一个attribute key,一个attribute value和一个delete button,它们共同构成一行。

该组件还有一个Add Attribute按钮,单击该按钮会在列表底部添加一个新行。

每个attribute key选择列表都有一个new attribute选项,在选择时会启动一个带有表单的模态对话框来输入新的属性名称,然后这个表单通过AJAX提交并返回一个ID,新的然后将选项附加到页面上的每个attribute key选项,以允许选择它。

当在组件的实例中选择了一个键时,组中的所有其他attribute key选择都会禁用该选项以防止重复的属性。

属性编辑器作为下面主表单的一部分提交。

<小时/>

主要表格

enter image description here
该组件由变体的一般描述字段组成。 表单通过AJAX提交,并附加jQuery Validation Engine表单验证。

因为我们使用属性编辑器动态添加新输入,所以我们必须不断分离并重新附加验证引擎。


警报系统

enter image description here
此组件处理在表单上显示/隐藏错误/成功/状态消息。

<小时/>

问题

现在还有一些非常相似的形式,但是在几个事件处理程序上有轻微的变化,所以我想创建代码,这样我就可以随意替换它的一些部分,而不必复制整个代码。

因此,在按照this question提示后,我最终得到了以下代码,但我收到错误:Uncaught TypeError: object is not a function就在这一行:var variantAlert = new VariantAlert(form);我相信是因为我没有回复任何东西,但我不知道应该返回什么来获取代码来做我想做的事情!

短版

$(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);
    }


})();

完整版

$(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);
        });
    }


})();

2 个答案:

答案 0 :(得分:1)

您的variantAlert使用类似

 variantAlert.message('failed');

这意味着构造函数必须返回包含消息函数

的对象
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  
};
}

答案 1 :(得分:0)

最后使用不同的方法让它工作,我的代码在下面供任何人比较和参考。

$(function () {

    /*********************************
     * Loop over each variant and setup
     * the attribute editor and form
     *********************************/
    var editors = [];
    $("form.variant-form").each(function (i, form) {
        var variantEditor = new VariantEditor(form);
        editors.push(variantEditor);
        variantEditor.attributeEditor.init();
        variantEditor.form.init();
    });
});

var VariantEditor = function (form) {
    var that = this;
    that.formElement = $(form);

    /*********************************
     * Sets up the attribute editor
     *********************************/
    that.attributeEditor = {

        /*********************************
         * Variables
         *********************************/
        template:null,
        attribute:null,
        modal:null,
        manualHide:false,
        triggerSelect:null,
        oldOption:null,

        /*********************************
         * Sets up the attribute editor
         *********************************/
        init:function () {

            var that = this;

            //The Template row
            that.template = $('.variant_demo_row', that.formElement);

            //How many attributes are pre-loaded
            that.attributes = $('.variant_select', that.formElement).length;

            //Append the attribute editor modal to the body to avoid style corruption
            that.modal = that.formElement.siblings('.newAttribute').appendTo('body');

            //setup the handlers
            //doing it this way allows us to overwrite the individual handlers with ease
            that.addNewAttributeHandler();
            that.removeAttributeHandler();
            that.selectFocusHandler();
            that.selectChangeHandler();
            that.attributeVisibilityHandler();
            that.modalFormSubmissionHandler();
            that.modalShowHandler();
            that.modalCancelClickHandler();

            $('.variant_select', that.formElement).each(function (i, select) {
                that.oldOption = null;
                $(select).change();
            });
        },

        /*********************************
         * Add new attribute button handler
         *********************************/
        addNewAttributeHandler:function () {
            var that = this;
            $('.variant_attribute_add_new a', that.formElement).on('click keypress', function (e) {
                //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 = that.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_' + that.attributes);
                $('.variant_input', newRow).prop('name', 'attribute_value_' + that.attributes);
                $('.variant_visible', newRow).prop('name', 'attribute_visible_' + that.attributes);

                //insert the new attribute row at the bottom of the attributes
                newRow.insertBefore($('.variant_attribute_add_new', that.formElement)).show('fast', function () {
                    $('select', newRow).focus();
                });

                //we have added new nodes so we need to reset the validationEngine
                that.formElement.validationEngine('detach');
                that.form.init();
                that.attributes++;
            });
        },

        /*********************************
         * Remove attribute button handler
         *********************************/
        removeAttributeHandler:function () {
            var that = this;

            that.formElement.on('click keypress', '.removeAttribute', {}, function (e) {

                //patched support for enter key
                if (e.type === 'keypress' && e.which != 13) {
                    return true;
                }

                that.attributes--;

                var val = $(this).siblings('select').val();

                //re-enable whatever attribute key was in use
                if (val != "") {
                    $('.variant_select option[value=' + val + ']', that.formElement).removeAttr('disabled');
                }

                //animate the removal of the attribute
                $(this).closest('.controls-row').hide('fast', function () {
                    $(this).remove();
                });
            });
        },

        /*********************************
         * Attribute key select focus handler
         *********************************/
        selectFocusHandler:function () {
            var that = this;

            that.formElement.on('focus', '.variant_select', {}, function () {
                //store the old option so we know what option to
                //re-enable if a change is made
                that.oldOption = $('option:selected', this).val();
            });
        },

        /*********************************
         * Attribute key select change handler
         *********************************/
        selectChangeHandler:function () {
            var that = this;
            that.formElement.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 (that.oldOption !== null) {
                    $('.variant_select option[value=' + that.oldOption + ']', that.formElement).removeAttr('disabled');
                }

                if ($('option:selected', select).hasClass('newAttribute')) { //Add new attribute selected
                    that.triggerSelect = select;
                    that.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', that.formElement).not(select).children('option[value=' + select.val() + ']').prop('disabled', 'disabled');
                }
                that.oldOption = select.val();
            });
        },

        /*********************************
         * Toggle visibility button handler
         *********************************/
        attributeVisibilityHandler:function () {
            var that = this;

            that.formElement.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
         *********************************/
        modalFormSubmissionHandler:function () {
            var that = this;

            $('.newAttributeForm', that.modal).validationEngine('attach', {
                onValidationComplete:function (form, status) {
                    if (status) {
                        var text = $('.newAttributeName', that.modal).val();
                        $('.newAttributeName', that.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
                                that.triggerSelect.val(data.id);
                                that.triggerSelect.trigger('change');


                                that.manualHide = true;
                                that.modal.modal('hide');
                                that.triggerSelect.siblings('input').focus();
                                form.spin(false);
                            },
                            dataType:'JSON'
                        });

                    }
                }});
        },

        modalCancelClickHandler:function () {
            var that = this;

            $('.btn-danger', that.modal).on('click', function () {
                if (!that.manualHide) {
                    that.triggerSelect[0].selectedIndex = 1;
                    that.triggerSelect.trigger('change');
                }
                that.manualHide = false;
            });
        },
        modalShowHandler:function () {
            var that = this;

            that.modal.on('show shown', function () {
                $('.newAttributeName', that.modal).focus();
            });
        }
    };


    /*********************************
     * Sets up the variant main form
     * The above function focuses on setting up the attribute editor
     * This function sets up the rest of the form and handles the
     * form submissions
     *********************************/
    that.form = {

        init:function () {
            var that = this;

            that.nameChangeHandler();
            that.submitHandler();
            that.statusChangeHandler();

        },

        /*********************************
         * Variant name change handler
         * Changes the heading on the accordion if the
         * name form input changes
         *********************************/
        nameChangeHandler:function () {
            var that = this;

            var accordion_heading = that.formElement.closest('.accordion-body').siblings('.accordion-heading').find('.accordion-toggle .name');
            $('.name-input', that.formElement).on('change', function () {
                accordion_heading.text($(this).val());
            });
        },

        statusChangeHandler: function(){
            var that = this;
            console.log($('input[name=status]', that.formElement).parent());
            $('input[name=status]', that.formElement).parent().on('click keypress', function(e){

                //patched support for enter key
                if (e.type === 'keypress' && e.which != 13) {
                    return true;
                }

                console.log('called');
                var checked = $(this).prop('checked');
                var label = that.formElement.closest('.accordion-body').siblings('.accordion-heading').find('.accordion-toggle .label');
                label.text(checked ? 'Online' : 'Offline').toggleClass('label-important label-success');
            });
        },

        /*********************************
         * Form submit handler
         *********************************/
        submitHandler:function () {
            var that = this;
            that.formElement.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) {
                                    that.message('failed');
                                } else {
                                    switch (response.status) {
                                        case 0:
                                            that.message('errors', response.errors);
                                            break;
                                        case 1:
                                            that.message('success');
                                            break;
                                        default:
                                            that.message('failed');
                                            break;
                                    }
                                }

                                form.spin(false);
                            },
                            error:function () {
                                cfox.alert('alert-error', "An error was encountered when submitting this form. Please try again.");
                                form.spin(false);
                                cfox.enableForm(form);
                            }
                        });
                    }
                }
            });
        }

    };

    /*********************************
     * handles showing/hiding any messages
     * in the variant forms
     *********************************/
    that.message = function (type, message) {
        var that = this;
        var alert;
        clearTimeout(that.timer);
        $('.variant_status_message', that.formElement).hide('fast');
        if (type == 'success') {
            alert = $('.variant_status_message.success', that.formElement);
        } else if (type == 'errors') {
            alert = $('.variant_status_message.errors', that.formElement);
            $('.alert-message', alert).html(message);
        } else if (type == 'failed') {
            alert = $('.variant_status_message.failed', that.formElement);
        }

        alert.show('fast', function () {

            $('html, body').animate({
                scrollTop:alert.closest('.accordion-group').offset().top
            }, 150, 'linear');

            that.timer = setTimeout(function () {
                alert.hide('fast')
            }, 5000);
        });
    }

    that.attributeEditor.formElement = that.formElement;
    that.attributeEditor.form = that.form;
    that.attributeEditor.message = that.message;

    that.form.formElement = that.formElement;
    that.form.attributeEditor = that.attributeEditor;
    that.form.message = that.message;

    that.message.formElement = that.formElement;
    that.message.attributeEditor = that.attributeEditor;
    that.message.form = that.form;
    return that;
};

VariantEditor.prototype = {};