Search code examples
javascriptjquerybackbone.js

Uncaught Type Error: View is not a constructor


I have Uncaught Type Error : UserRegisterView is not a constructor.I dont understand this error.I looked all code but i dont find it. Sorry of my bad english.Please help me

Thanks for answer

UPDATED

UserRegisterView is here

var UserRegisterView = Backbone.View.extend({

    model: User,
    el: '#form',
    events: {
        'click input[id="infoWeek"]': 'infoWeek',
        'click input[id="infoMonth"]': 'infoMonth'
    },

    infoWeek: function() {

        this.$el.find("#dayOfMonth").hide();
        this.render();
    },

    infoMonth: function() {

        this.$el.find("#dayOfWeek").hide();
        this.render();

    }
});

var AddUserView = Backbone.View.extend({
    el: $(".page"),
    events: {
        'click #saveUser': 'saveUser'
    },
    saveUser: function() {
        var user = new User();
        user.set({
            username: $("#username").val(),
            lastName: $("#lastName").val(),
            regNumber: $("#regNumber").val(),
            password: $("#password").val(),
            departmentName: $("#departmentName").val(),
            email: $("#email").val(),
            role: $("#role").val()
        });
        user.save();

        if (document.getElementById('isOpen').checked) {
            user.set("isOpen", $("#isOpen").val("1"));
            user.save();
        } else {
            user.set("isOpen", $("#isOpen").val("0"));
            user.save();
        }

        if (document.getElementById('dayOfWeek').checked) {
            user.set("dayOfWeek", $("#dayOfWeek").val());
            user.save();
        } else if (document.getElementById('dayOfMonth').checked) {
            user.set("dayOfMonth", $("#dayOfMonth").val());
            user.save();
        }


        $("#username").val("");
        $("#firstName").val("");
        $("#lastName").val("");
        $("#regNumber").val("");
        $("#password").val("");
        $("#deparmentName").val("");
        $("#email").val("");
        $("#isOpen").val("");
        $("#dayOfWeek").val("");
        $("#dayOfMonth").val("");
    },

    render: function() {
        var that = this;
        var template = Handlebars.compile(UserRegister);
        var myHtml = template(that.model.toJSON());
        that.$el.html(myHtml);
        return this;
    }
});

return {
    AddUserView: AddUserView,
    UserRegisterView: UserRegisterView
};

});

router user func.

define([
    'jquery',
    'underscore',
    'backbone',
    'handlebars',
    'spin',
    'app/models/LoginModel',
    'app/views/LoginView',
    'app/views/UserRegisterView'
], function($,
    _,
    Backbone,
    Handlebars,
    Spinner,
    Login,
    LoginView,
    UserRegisterView
) {

    var Router = Backbone.Router.extend({
        routes: {
            'search': 'search',
            'login': 'login',
            'travels': 'travels',
            'user': 'user',
            'menu': 'menu',
            '': 'home'

        },
        user: function() {

            disposeView(new UserRegisterView().render());

        }

dispose.view on util.js

function disposeView(view) {
    Backbone.View.prototype.close = function() {
        this.unbind();
        this.undelegateEvents();
    };

    /* Şu anki viewi yok et */
    if (this.currentView !== undefined) {
        this.currentView.close();
    }

    /* Yeni view oluştur. */
    this.currentView = view;
    this.currentView.delegateEvents();

    return this.currentView;
}

Solution

  • What's happening

    Your UserRegisterView module returns an object which contains two constructors.

    return {
        AddUserView: AddUserView,
        UserRegisterView: UserRegisterView
    };
    

    When using this module, what you're getting is the object above.

    define([
        // ...
        'app/views/UserRegisterView'
    ], function(
        // ...
        UserRegisterView // value of the return in the module
    ) {
    

    So you're kind of misleading yourself by calling it UserRegisterView as it's not the constructor, but the object containing the constructor.

    To get a new UserRegisterView view instance with the current way your module is setup, you'd need to call it like so:

    var userView = new UserRegisterView.UserRegisterView();
    

    Or to create a AddUserView instance:

    var addView = new UserRegisterView.AddUserView();
    

    Solutions

    • Split up the module, one for each view constructor.
    • Change the name so at least it's not misleading (like UserViewsModule)

    Other improvements

    That being said, there are other improvements that could be made to your Backbone code.

    var UserRegisterView = Backbone.View.extend({
        // that's useless (if not used) and not a view property.
        // model: User,
    
        // don't use `el` like that, especially when using the view as a shared Constructor
        el: '#form',
        events: {
            'click input[id="infoWeek"]': 'onInfoWeekClick',
            'click input[id="infoMonth"]': 'onInfoMonthClick'
        },
    
        initialize: function() {
            // Cache jQuery object of the view's element
            this.$dayOfMonth = this.$("#dayOfMonth");
            this.$dayOfMonth = this.$("#dayOfMonth");
            // also use the shortcut function instead of `this.$el.find()`
        }
    
        onInfoWeekClick: function(e) {
            this.$dayOfMonth.hide();
            // calling render here is useless unless your using it as a parent
            // view, where the child view overrides the render function.
        },
    
        onInfoMonthClick: function(e) {
            this.$dayOfMonth.hide();
        }
    });
    

    The disposeView function could be simplified:

    function disposeView(view) {
        var current = this.currentView;
        if (current) current.close();
        current = this.currentView = view;
        current.delegateEvents();
        return current;
    }
    

    Don't change the default Backbone view prototype each time the function is called. Instead, add the function once.

    _.extend(Backbone.View.prototype, {
        close: function() {
            this.unbind();
            this.undelegateEvents();
        },
        // any other function you want to add can go here.
    });
    

    In another answer, I go into details on how to extend Backbone's core classes with requirejs transparently.

    You're already using jQuery, so don't use JavaScript DOM API document.getElementById('isOpen') interspersed with jQuery selectors $('#isOpen').

    I made some improvements to the following view. Take the time to create yourself some utility functions (like reset and getValues) to simplify the flow of the code and encapsulate the complexity.

    var AddUserView = Backbone.View.extend({
        el: $(".page"),
        events: {
            'click #saveUser': 'saveUser'
        },
        // compile the template once while creating the view class
        template: Handlebars.compile(UserRegister),
    
        // get the selector string out of the code and place them in one place
        // easy to change and maintain.
        fields: {
            username: "#username",
            firstName: "#firstName",
            lastName: "#lastName",
            regNumber: "#regNumber",
            password: "#password",
            deparmentName: "#deparmentName",
            email: "#email",
            isOpen: "#isOpen",
            dayOfWeek: "#dayOfWeek",
            dayOfMonth: "#dayOfMonth",
        },
    
        render: function() {
            this.$el.html(this.template(this.model.toJSON()));
            // cache jQuery object of every field once after a render
            this.field = _.reduce(this.fields, function(fields, selector, key) {
                fields['$' + key] = this.$(selector);
                return fields;
            }, {}, this);
            return this;
        },
        reset: function() {
            // reset all the fields once without repeating code.
            _.each(this.field, function($field) {
                $field.val("");
            });
            return this;
        },
        getValues: function(keys) {
            // get the value of multiple fields returned in a nice object
            // ready to be sent to a Backbone model.
            return _.reduce(keys, function(data, key) {
                data[key] = this.field[key].val();
                return data;
            }, {}, this);
        },
    
        saveUser: function() {
            var field = this.field,
                user = new User(this.getValues([
                    'username',
                    'lastName',
                    'regNumber',
                    'password',
                    'departmentName',
                    'email',
                    'role',
                ]));
    
            user.set({ isOpen: field.$isOpen.is(':checked') });
    
            if (field.$dayOfWeek.is(':checked')) {
                user.set("dayOfWeek", field.$dayOfWeek.val());
            } else if (field.$dayOfMonth.is(':checked')) {
                user.set("dayOfMonth", field.$dayOfMonth.val());
            }
    
            user.save();
            this.reset();
        },
    });
    

    In the following snippet, you're putting the context (this) into a local variable. I see that a lot and I could say that 90% of the times I see it on Stack Overflow questions, it makes no sense. It clearly screams copy-pasted.

    render: function() {
        var that = this;
        // ...
        that.$el.html(myHtml);
        return this;
    }
    

    Please tell me you see that you're putting this into that, then using that throughout the function, then you still return this?!

    Putting the context into a local variable is useful when the object is needed in a dynamically created callback.

    render: function() {
        var that = this; // this is available here
        setTimeout(function() {
            // here this is not available.
            that.handleCallback();
        }, 10);
        // here we are in the same context as the first line.
        return this;
    }