How do I customize knockout mapping creation in ne

2019-05-24 04:06发布

问题:

Completely new to Knockout and I am trying to map a JSON response from the server to specific models using the knockout mapping plugin. The models are nested and I'm trying to override object construction using the create callback even in the nested models. However, it doesn't appear that my mapping options are being read properly. Example JSON:

{
    "EmployeeFeedbackRequestSubmissions": [
        {
            "EmployeeFeedbackRequestSubmissionId": 0,
            "Employee": "John Smith0",
            "EmployeesWorkedWith": [
                {
                    "EmployeeName": "Joe Smith",
                    "ProjectsWorked": [
                        {
                            "ProjectName": "Document Management Console"
                        },
                        {
                            "ProjectName": "Performance Eval Automation"
                        },
                        {
                            "ProjectName": "Business Tax Extensions"
                        }
                    ]
                },
                {
                    "EmployeeName": "Michael Jones",
                    "ProjectsWorked": [
                        {
                            "ProjectName": "Document Management Console"
                        },
                        {
                            "ProjectName": "Performance Eval Automation"
                        },
                        {
                            "ProjectName": "Business Tax Extensions"
                        }
                    ]
                },
                {
                    "EmployeeName": "Jason Smith",
                    "ProjectsWorked": [
                        {
                            "ProjectName": "Document Management Console"
                        },
                        {
                            "ProjectName": "Performance Eval Automation"
                        },
                        {
                            "ProjectName": "Business Tax Extensions"
                        }
                    ]
                },
                {
                    "EmployeeName": "Robert Will",
                    "ProjectsWorked": [
                        {
                            "ProjectName": "Document Management Console"
                        },
                        {
                            "ProjectName": "Performance Eval Automation"
                        },
                        {
                            "ProjectName": "Business Tax Extensions"
                        }
                    ]
                }
            ]
        }
        // more EmployeeFeedbackRequestSubmissions
    ]
}

Mapping options:

var mappingOptions = {
    // overriding default creation/initialization code
    'EmployeeFeedbackRequestSubmissions': {
        create: function (options) {
            return (new(function () {
                this.EmployeeHeading = ko.computed(function () {
                    return "Performance Evaluation Employee: " + this.Employee();
                }, this);

                ko.mapping.fromJS(options.data, {}, this);
            })());
        },
            'EmployeesWorkedWith': {
            create: function (options) {
                return new instance.EmployeesWorkedWithModel(options.data);
            }
        }
    }
};

Sample fiddle with full example: http://jsfiddle.net/jeades/9ejJq/2/

The result should be the ability to use the computed nameUpper from the EmployeesWorkedWithModel. I'm also open to suggestions about a better way to do this as this may not be the best way to handle this.

回答1:

You were almost there. Straight to it working: http://jsfiddle.net/jiggle/na93A/

The mappings options object doesn't need to be nested, the mapping plug in will look up the mapping from the name, when you pass them to ko.mapping.fromJSON

So your mapping options object should be single level:

    var self = this;

    self.mappingOptions = {
        // overriding default creation/initialization code
        'EmployeeFeedbackRequestSubmissions': {
            create: function (options) {
                return (new(function () {
                    this.EmployeeHeading = ko.computed(function () {
                        return "Performance Evaluation Employee: " + this.Employee();
                    }, this);

                    ko.mapping.fromJS(options.data, self.mappingOptions, this);
                })());
            }
        },
        'EmployeesWorkedWith': {
                create: function (options) {
                   // return new instance.EmployeesWorkedWithModel(options);
                    return (new(function(){
                        ko.mapping.fromJS(options.data, {}, this);

                        this.nameUpper = ko.computed(function () {
                            return this.EmployeeName().toUpperCase();
                        }, this);
                    })());
                }
        }

    };

Notice I have used "self" as your local reference to 'this' instead of 'instance', just to make the code easier to read (as you used 'instance' in the main viewmodel).

I have also made the mappingOptions object part of the FeedbackViewModel, as we need to pass this into the mapping.fromJS call so when it sees the 'EmployeesWorkedWith' level in the data it will have the mappingOptions for it.

From:

ko.mapping.fromJS(options.data, {}, this);

To:

ko.mapping.fromJS(options.data, self.mappingOptions, this);

You can then move your creation code for 'EmployeesWorkedWith' level into the create (you could call a function, but I've kept it together in the mappingOptions as shown above, like the way you were creating the 'EmployeeFeedbackRequestSubmissions' level.

You can then get rid of the instance.EmployeesWorkedWithModel function altogether.

A working fiddle can be found here:

http://jsfiddle.net/jiggle/na93A/

Alternatively, you could create separate mappingOptions object when you are in the create for 'EmployeeFeedbackRequestSubmissions' and not have the mappings for all levels in one object, which can be seen in this fiddle http://jsfiddle.net/jiggle/Avam7/

Depends on your coding style which way you prefer, and would be important to separate them out if you had different mapping needs for different levels and they had the same collection name.

eg.

Employees

Employee

Employees (you might need different computeds, etc. at this level)

If so, you would use the second option (separate the mappingOptions and pass to the level that will use it)

I've added some console.log statements to the fiddles so you can see values as the code runs in the console, which will help to understand how it's working.

Hope it helps.



回答2:

Nice thing with ko.mapping is how automated the process can be. Check out the results in http://jsfiddle.net/9ejJq/26/

You'll note how we only use one declared mapping to kick things off.

feedbackMappingOptions = {
    create: function (options) {
        return new FeedbackViewModel(options.data);
    }
};

From there on, each view model triggers a mapping for their child objects. You could go as far as creating a mapping option for each or, as you see for the final ProjectsWorked object under the EmployeesWorkedWith, we just throw the data right at a mapping and ko.mapping does the rest. Hope this helped.