Gabriel Sadaka
Gabriel Sadaka

Reputation: 1746

Knockout Mapping Observable Array MVC

Hi I have looked around online for hours trying to solve this but I couldn't find a solution.

I am building the solution using ASP.NET MVC and Knockout. The controller returns the options for the user to select from to the view which converts it into JSON and uses the mapping plugin to map it to a knockout View Model. It populates correctly because when I do this: ko.toJSON($data) to test it, the correct data is returned but the view won't update the list when an item is added to it.

My guess is that the properties aren't observables but I'm not sure how to fix that.

This is an example of the JSON Returned:

[{"Key":1,"Value":"company"}]

This is my javascript:

function ProjectWorkedOn() {
    var self = this;
    self.client = ko.observable();
    self.job = ko.observable();
    self.project = ko.observable();
    self.workType = ko.observable();
}

function createTimesheetViewModel() {
    var self = this;

    //list of options
    self.UserClients = ko.observableArray();
    self.UserProjects = ko.observableArray();
    self.UserJobs = ko.observableArray();
    self.UserWorkTypes = ko.observableArray();

    //keep track of selected options
    self.selectedClient = ko.observable();
    self.selectedProject = ko.observable();
    self.selectedJob = ko.observable();
    self.selectedWorkType = ko.observable();

    //list to add choices in
    self.ListProjectsWorkedOn = ko.observableArray();


    self.addProjectWorkedOn = function () {
        var project = new ProjectWorkedOn();
        project.client = self.selectedClient;
        project.job = self.selectedJob;
        project.project = self.selectedProject;
        project.workType = self.selectedWorkType;
        self.ListProjectsWorkedOn.push(project)
    }
    self.removeProjectWorkedOn = function (projectWorkedOn) {
        self.ListProjectsWorkedOn.remove(projectWorkedOn)
    }

}



$(function () {
    var CreateTimesheetViewModel = new createTimesheetViewModel();

    CreateTimesheetViewModel.UserClients = ko.mapping.fromJSON('@Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model.UserClients))');
    CreateTimesheetViewModel.UserProjects = ko.mapping.fromJSON('@Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model.UserProjects))');
    CreateTimesheetViewModel.UserJobs = ko.mapping.fromJSON('@Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model.UserJobs))');
    CreateTimesheetViewModel.UserWorkTypes = ko.mapping.fromJSON('@Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model.UserWorkTypes))');

    ko.applyBindings(CreateTimesheetViewModel, document.getElementById("CreateTimesheet"));
});

This is the part of the view that adds items to the list:

<table>
    <tr>
        <td>
            Projects Worked On:
        </td>
        <td>
            Client: <select data-bind="options: UserClients, optionsText: 'Value', value: selectedClient"></select>
        </td>
        <td>
            Project: <select data-bind="options: UserProjects, optionsText: 'Value', value: selectedProject"></select>
        </td>
        <td>
            Job: <select data-bind="options: UserJobs, optionsText: 'Value', value: selectedJob"></select>
        </td>
        <td>
            Service: <select data-bind="options: UserWorkTypes, optionsText: 'Value', value: selectedWorkType"></select>
        </td>
        <td>
            <button data-bind="click: addProjectWorkedOn">Add Project</button>
        </td>
    </tr>
</table>

and this is what displays them:

<table>
    <tr>
        <td>Client</td>
        <td>Project</td>
        <td>Job</td>
        <td>Service</td>
        <td></td>
    </tr>
    <tbody data-bind="foreach: ListProjectsWorkedOn()">
        <tr>
            <td data-bind="text: client.Value"></td>
            <td data-bind="text: project.Value"></td>
            <td data-bind="text: job.Value"></td>
            <td data-bind="text: workType.Value"></td>
            <td><button data-bind="click: $parent.removeProjectWorkedOn">Remove</button></td>
        </tr>
    </tbody>
</table>

Thanks in advance!

EDIT:

I worked it out

to add project set the attributes like this: project.clientValue(self.selectedClient().Value);

then to reference them in view call it as a function: clientValue()

credit goes to Chris Patt for explaining that observables should be called as functions when you want to get their value

Upvotes: 0

Views: 1408

Answers (1)

Chris Pratt
Chris Pratt

Reputation: 239440

Because you're setting the ProjectWorkedOn observables and getting the selected values incorrectly. To set an observable you pass the value as a parameter as you would for a function and to get the value of an observable, you call it like a function. (They are in fact functions.):

self.addProjectWorkedOn = function () {
    var project = new ProjectWorkedOn();
    project.client(self.selectedClient());
    project.job(self.selectedJob());
    project.project(self.selectedProject());
    project.workType(self.selectedWorkType());
    self.ListProjectsWorkedOn.push(project)
}

EDIT

I was thinking you literally wanted the selected values, not the actual whole JSON object. What you're attempting simply isn't possible. The value of the select has to be an integer or string -- it can't be a full object. Usually you would handle something like this by first simply returning a value/text list to populate the selects, where the value would be something like the id. Then, upon selection, you would issue an AJAX request to retrieve the object with that id.

Upvotes: 1

Related Questions