Search code examples
javascriptrequirejsdurandal

RequireJS / Durandal fields always null


PROBLEM

Durandal or perhaps RequireJS seems to be making my properties null. My main view model has 3 sub models (PostModel, CategoryModel, TagModel) and all of these end up being empty objects.

CODE

Main File: blog.js

var viewModel;

define(['jquery', 'knockout', 'datajs', 'OData', 'Q', 'breeze', 'blog-posts', 'blog-categories', 'blog-tags'],
    function ($, ko, datajs, odata, Q, breeze, posts, categories, tags) {
        'use strict'

        var ViewModel = function () {
            var self = this;

            self.postModel = false;
            self.categoryModel = false;
            self.tagModel = false;

            self.activate = function () {
                self.postModel = new posts();
                self.categoryModel = new categories();
                self.tagModel = new tags();
            };
            self.attached = function () {
                breeze.config.initializeAdapterInstances({ dataService: "OData" });
                self.postModel.init();
                self.categoryModel.init();
                self.tagModel.init();
            };
            self.showCategories = function () {
                //.....
            };
            self.showPosts = function () {
                //.....
            };
            self.showTags = function () {
                //.....
            };
        };

        viewModel = new ViewModel();
        return viewModel;
    });

post.js

define(['jquery', 'jqueryval', 'knockout', 'kendo', 'kendo-knockout', 'notify'],
    function ($, jqueryval, ko, kendo, ko_kendo, notify) {
        'use strict'

        var PostModel = function () {
            var self = this;

            self.id = ko.observable(0);
            self.categoryId = ko.observable(0);
            self.headline = ko.observable(null);
            self.slug = ko.observable(null);
            self.teaserImageUrl = ko.observable(null);
            self.shortDescription = ko.observable(null);
            self.fullDescription = ko.observable(null);
            self.useExternalLink = ko.observable(false);
            self.externalLink = ko.observable(null);
            self.metaKeywords = ko.observable(null);
            self.metaDescription = ko.observable(null);

            self.availableTags = ko.observableArray([]);
            self.chosenTags = ko.observableArray([]);

            self.init = function () {
                //..... this code runs fine (initializes a Kendo Grid)
            };
            self.create = function () {
                //.....
            };
            self.edit = function (id) {
                //.....
            };
            self.remove = function (id) {
                //.....
            };
            self.save = function () {
                //.....
            };
            self.cancel = function () {
                //.....
            };
            self.validator = $("#post-form").validate({
                rules: {
                    // JQuery Validation Rules
                }
            });
        };
        return PostModel

tag.js

define(['jquery', 'jqueryval', 'knockout', 'kendo', 'kendo-knockout', 'notify'],
    function ($, jqueryval, ko, kendo, ko_kendo, notify) {
        'use strict'

        var TagModel = function () {
            var self = this;

            self.id = ko.observable(0);
            self.name = ko.observable(null);
            self.urlSlug = ko.observable(null);

            self.init = function () {
                //..... this code runs fine (initializes a Kendo Grid)
            };
            self.create = function () {
                //.....
            };
            self.edit = function (id) {
                //.....
            };
            self.remove = function (id) {
                //.....
            };
            self.save = function () {
                //.....
            };
            self.cancel = function () {
                //.....
            };
            self.validator = $("#tag-form").validate({
                rules: {
                    // JQuery Validation Rules
                }
            });
        };
        return TagModel;
    });

category.js

define(['jquery', 'jqueryval', 'knockout', 'kendo', 'kendo-knockout', 'notify'],
    function ($, jqueryval, ko, kendo, ko_kendo, notify) {
        'use strict'

        var CategoryModel = function () {
            var self = this;

            self.id = ko.observable(0);
            self.name = ko.observable(null);
            self.urlSlug = ko.observable(null);

            self.init = function () {
                //..... this code runs fine (initializes a Kendo Grid)
            };
            self.create = function () {
                //.....
            };
            self.edit = function (id) {
                //.....
            };
            self.remove = function (id) {
                //.....
            };
            self.save = function () {
                //.....
            };
            self.cancel = function () {
                //.....
            };
            self.validator = $("#category-form").validate({
                rules: {
                    // JQuery Validation Rules
                }
            });
        };
        return CategoryModel;
    });

NOTES

  • Each of the init() functions works fine, since they initialize kendo grids and I can see them working fine.
  • When I try call create() on any of them, they throw errors about the fields being null. Example: self.validator is undefined (I call reset on the validator inside create()).
  • So what I did was run the following inside of FireBug: alert(JSON.stringify(viewModel.postModel)); This just shows {}, so we can see an empty object here.

WHAT I HAVE TRIED

  • I tried changing everything to singleton, for example:

var CategoryModel = {}

instead of:

var CategoryModel = function () {};

That seemed to prevent the viewModel.postModel and others from being null, BUT it caused other problems. Mostly scoping problems with the this keyword and I couldn't set var self = this; anywhere successfully - even when I did it in activate(), it would still not work - I would get the main view model or something else when accessing this. So I think the way I have it setup now is the safest regarding scoping, but for some reason those 3 fields are always NULL.

I have been tearing my hair out for days on this one. Can anyone help me?

EDIT

When I run the following in the console:

alert(JSON.stringify(viewModel))

I get:

{"postModel":{},"categoryModel":{},"tagModel":{},"__moduleId__":"viewmodels/admin/blog"}

As you can see, the 3 child models are empty objects and for some reason, the following functions are missing as well:

self.showCategories = function () { }
self.showPosts = function () { }
self.showTags = function () { }

I have completely run out of ideas on this one...


Solution

  • Alright, for some reason self.validator was always NULL. My guess is the module is being evaluated before the HTML is injected into the Durandal placeholder. Therefore, when self.validator = $("#post-form").validate({ is being evaluated, the form with ID #post-form does not exist on the page and therefore self.validator is undefined.

    So my solution was to only assign self.validator in my init() function which is called during the attached callback in the Durandal lifecycle.