简体   繁体   中英

Json data with collection of objects gets mapped as observable instead of observable array

On my page, I want to receive a viewmodel of a user account via Json. The data consists of some user details such as real name and email addresse etc. Besides that, I also need to show (and change) the groups which the specific user is a member of, and a list of all available user groups to select additional ones for the user.

The data gets loaded properly and the user details show up as expected. For the group assignment, I created two list boxes. The first one shows the currently assigned groups, the other one shows all existing groups. I want to use two buttons to move a selected item from the "All Groups" list to the "Selected Groups" list ("Add to selection") and another one to remove an item from the "Selected Groups" list and put it back into the "All Groups" list.

Both lists get populated correctly. When I look into my viewModel at runtime, I can see that viewModel.AllGroups() is an observable with a number of group objects in its _latestValues (a group object consists of an Id and a GroupName ). I can also properly access them directly, eg with a button click for testing that triggers

alert(viewModel.AllGroups()[0].GroupName());

This gives the right text, so basically everything is in place. But when I try to manually add a group object to SelectedGroups collection, it seems that viewModel.SelectedGroups and viewModel.AllGroups do not have .push or .remove because they are not an observable array but just an observable!?

Here's my JS code:

<script type="text/javascript">
    var viewModel;

    $(function () {

        $.ajaxSetup({ cache: false });

        $.getJSON('/Admin/GetEditWebUserData?Id=@WebUserId', function (data) {
            viewModel = ko.mapping.fromJS(data);
            Selected = viewModel.SelectedGroups();

            viewModel.addToSelection = function () {
                viewModel.SelectedGroups().push({ GroupName: 'Test Group', Id: '123' });
            }

            ko.applyBindings(viewModel, document.getElementById('data'));
        });
    });
</ script>

... and here's the HTML part with the bindings:

<div id="data">
    <div class="WebUserGroupEditBox">
        Member of:
        <select size="5" data-bind="options: SelectedGroups(), optionsText: 'GroupName', optionsValue: 'Id'"></select>
        <button data-bind="click: removeFromSelection">remove from selection</button>
    </div>
    <div class="WebUserGroupEditBox">
        Available groups:
        <select multiple="multiple" size="5" data-bind="options: AllGroups(), optionsText: 'GroupName', optionsValue: 'Id'"></select>
        <button data-bind="click: addToSelection">add to selection</button>
    </div>
</div>

When I click the add to selection button, I get an error telling me that .push does not exist. I think this might me an issue with the mapping. Is there something I can do other than implementing the mapping on my own?

Update: This is some Json data (sniffed directly from the server response):

{
   "FirstName":"Rob",
   "LastName":"Smith",
   "UserName":"rsmith",
   "Email":"rsmith@test.com",
   "SelectedGroups":null,
   "AllGroups":[
      {
         "Id":0,
         "GroupName":"Managers",
         "AllowCommands":true,
         "AllowParameters":true
      },
      {
         "Id":1,
         "GroupName":"Guests",
         "AllowCommands":false,
         "AllowParameters":false
      }
   ]
}

Solution:

The problem was not the mapping itself, but the server-side code that constructs the Json data. As nemesv pointed out in his comment, there needs to be an empty array instead of null when there are no items, so that the mapping plugin properly recognizes the structure of the data.

The problem is in your JSON coming form the server:

"SelectedGroups":null,

Because your property is null the mapping plugin has no idea that you want an array here it just create a ko.observable for you.

To fix it you need to change your server side to send an empty array instead of null :

"SelectedGroups": [],

in this case the mapping plugin will create an empty ko.observableArray .

However if you cannot change the server side you can also tell the mapping plugin how to map the "SelectedGroups" property using the create mapping option :

var mapping = {
    "SelectedGroups": {
        create: function(options){
            if (!options.data) // no data from the server return an empty array
                return ko.observableArray();
            // SelectedGroups is not empty continue the mapping
            return ko.mapping.fromJS(options.data);
        }
    }
};

var vm = ko.mapping.fromJS(data, mapping);

Demo JSFiddle .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM