0

I am having some trouble with knockout and hoped someone here might be able to help.

I have two jQuery functions. One which, using knockout, binds to a single element on the page.The other binds to the rest of the elements and then calls the first function.

The data is taken from an AJAX request which returns Json.

The problem I am having is to doing with pGroups list. It works fine the first time but then when you click again it fails and needs a refresh to work again.

The Console error is: NotFoundError: Node was not found

EDIT: Updated code to show progress

The jQuery is:

//Load user data into the action window when a user is selected
$('.ListUserLink').click(function () {

    var url = '@Url.Action("DisplayUser", "AjaxUser")' + '?UserId=' + $(this).attr("UserId") + '&UserNum=' + $(this).attr("UserNum") + "&SectId=" + $(this).attr("Sect");

    $.ajax({
        url: url,
        contentType: "application/json; charset=utf-8",
        type: 'POST',
        context: this,
        timeout: 60000,
        dataType: 'json',
        tryCount: 0,
        retryLimit: 3,
        success: function (data) {
            //ko.applyBindings(new UserViewModel(data));
            viewModel.user = new userModel(data);
        },
        error: function (httpRequest, textStatus, errorThrown) {
            alert("Error");
        }
    });
});

//Load sections on department index change
$("#ddbDepartments").change(function () {
    var url = '@Url.Action("GetSectionsByDept", "AjaxUser")' + '?deptId=' + $(this).val();
    $.ajax({
        url: url,
        contentType: "application/json; charset=utf-8",
        type: 'POST',
        context: this,
        timeout: 60000,
        dataType: 'json',
        tryCount: 0,
        retryLimit: 3,
        success: function (data) {
            //ko.applyBindings(new SectionViewModel(data), $(".SectionsDDB")[0]);
            viewModel.sections = new userModel(data);
        },
        error: function (httpRequest, textStatus, errorThrown) {
            alert("Error");
        }
    });
});



//Assign Section details to fields
function sectionsModel(data) {
    this.sectionList = ko.observableArray(data.SectionList);
//      this.sections = this.sectionList;
//      this.selectedItem = parseInt($("#OldSectionId").value);
};

//Assign user details to fields
function userModel(data) {

    this.fullName = ko.observable(data.FirstName + " " + data.Surname);

    this.firstName = ko.observable(data.FirstName);
    this.surname = ko.observable(data.Surname);
    this.usernum = ko.observable(data.UserNum);

    //Assign JobTitle Dropdown and selected value
    this.jobTitlesList = ko.observableArray(data.TitlesList);
    this.jobTitles = this.jobTitlesList;
    this.selectedItem = data.JobTitleNum;

    //Assign Group/Application list
    this.pGroups = ko.observableArray(data.GroupList);

    this.sections = ko.observableArray([{}]);

    this.ext = ko.observable(data.Ext);
    this.userId = ko.observable(data.UserId);
    this.olduserid = ko.observable(data.UserId);
    $("#ddbDepartments").val(data.DeptId);
    this.oldsectionid = ko.observable(data.SectionId);
    $("#ddbDepartments").change();
    this.oldsectionid = ko.observable(data.SectionId);
    //$("#SectionsDDB").val(data.SectionId);
};

var wrapper = function () {
    this.user = new userModel(userdata);
    this.sections = new sectionsModel(sectiondata);
};

var viewModel = new wrapper();
    ko.applyBindings(viewModel);

The pGroups HTML which is failing on the second attempt is:

 <div data-bind="with: user" id="ActionWindow">

<form action="@Url.Action("SaveUserDetails", "AJAXUser")" method="post" class="AjaxSubmit" id="userDetailsForm">
    <h2>User: <span data-bind="text: fullName"></span></h2>
    <table>
        <tr>
            <td>First Name:</td>
            <td><input  type="text" name="FirstName" id="FirstName" data-bind="value: firstName" /></td>
            <td>
                <input type="hidden" name="UserNum" id="UserNum" data-bind="value: usernum" />
                <input type="hidden" name="OldUserId" id="OldUserId" data-bind="value: olduserid" />
                <input type="hidden" name="OldSectionId" id="OldSectionId" data-bind="value: oldsectionid" />
            </td>
            <td></td>
        </tr>

        <tr>
            <td>Surname:</td>
            <td><input type="text" name="Surname" id="Surname" data-bind="value: surname" /></td>
            <td></td>
            <td></td>
        </tr>

        <tr>
            <td>Job Title:</td>
            <td><select name="JobTitleNum" id="TitlesList" data-bind="options: jobTitles, optionsValue: 'TitleId', optionsText: 'Title', value: selectedItem"></select></td>
            <td></td>
            <td></td>
        </tr>

        <tr>
            <td>Extension:</td>
            <td><input type="text" name="Ext" id="Ext" data-bind="value: ext" /></td>
            <td></td>
            <td></td>
        </tr>

        <tr>
            <td>Login ID:</td>
            <td><input type="text" name="UserId" id="UserId" data-bind="value: userId" /></td>
            <td></td>
            <td></td>
        </tr>

        <tr>
            <td>Department:</td>
            <td>
                <select id="ddbDepartments" name="DeptId">
                    @foreach (var d in Model.DepartmentList)
                    {
                        <option value="@d.DeptId">@d.DeptName</option>
                    }
                </select>
            </td>
            <td>Section: </td>
            <td>
                <select name="SectionId" class="SectionsDDB" data-bind="options: $root.sections.list, optionsValue: 'SectId', optionsText: 'SectName', value: SectionId"></select>
                @*<select name="SectionId" class="SectionsDDB" data-bind="options: sections, optionsValue: 'SectId', optionsText: 'SectName', value: selectedItem"></select>*@
            </td>
        </tr>
    </table>
    <input type="submit" value="Update User" />
    <br />
</form>

<h2>Current Groups</h2>
<table>
    <tbody data-bind="foreach: pGroups">
        <tr>
            <td data-bind="text:AppName"></td>
            <td data-bind="text:GroupName"></td>
        </tr>
    </tbody>
</table>

Everything else is working.

I did find this: http://knockoutjs.com/documentation/plugins-mapping.html

WHich indicates I should be mapping like this:

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

but try as I might I cannot figure out how to apply this to my code.

Any help woud be greatly appreciated.

tereško
  • 58,060
  • 25
  • 98
  • 150
Lex Eichner
  • 1,056
  • 3
  • 10
  • 35
  • The ko.applyBindings function should not be called every time you change the department. Each time you invoke ko.applyBindings the entire DOM is inspected for bindings. As a result you will get multiple bindings for the same element if you do this more than once. Please check this link in order to make sure you understand how you can have multiple views on the same page: http://stackoverflow.com/questions/8676988/example-of-knockoutjs-pattern-for-multi-view-applications/8680668#8680668 – Flavia Obreja Feb 24 '14 at 11:55
  • Please add a jsfiddle in case you don't manage to solve the issue – Flavia Obreja Feb 24 '14 at 11:58
  • So I need to remove the ko.apply bindings from the AJAX success and make it map in there instead? The examples in your link seem to be for single calls for seperate parts of the Dom where as I have some overlap. Here is a jsFiddle but I cant for the life of me get it to work http://jsfiddle.net/dariune/8djDw/ – Lex Eichner Feb 24 '14 at 15:29
  • You should replace viewModel.sections = new userModel(data); with viewModel.sections = new sectionsModel(data); – Flavia Obreja Feb 25 '14 at 11:32

3 Answers3

1

Based on the link i provided in the previous comment(http://www.knockmeout.net/2012/05/quick-tip-skip-binding.html) i made an example of your possible view model:

//the overall model
var wrapper = function () {
    this.user = ko.observable();
    this.user(new userModel(userdata));
    this.sections = new sectionsModel(sectiondata);
};
var viewModel = new wrapper();
ko.applyBindings(viewModel);

And then in the success of $('.ListUserLink').click you can have:

viewModel.user(new UserViewModel(data));

And in the success of $("#ddbDepartments").change you can have:

viewModel.sections.sectionList(data.SectionList);

In the view, you can use the with binding along with $root to bind a nested view model:

  <div data-bind="with: user">
        <h2>User: <span data-bind="text: fullName"></span></h2>
        ...
      <select name="SectionId" class="SectionsDDB" data-bind="options: $root.sections.sectionList, optionsValue: 'SectId', optionsText: 'SectName', value: $root.sections.selectedValue"></select>
        ...
    </div>
Flavia Obreja
  • 1,227
  • 7
  • 13
  • Thanks I have done as the link said and I think I m almost there and this time I am missing something daft. I get Error: Unable to parse bindings. for fullName (And I imagine the rest). I have updated my jsfiddle to show this: http://jsfiddle.net/dariune/8djDw/2/ – Lex Eichner Feb 25 '14 at 10:35
  • I made a change: http://jsfiddle.net/8djDw/5/. I also added the jquery library to be able to set also the value of ddbDepartments and added the SectionId property to the userModel – Flavia Obreja Feb 25 '14 at 10:54
  • If you want to have the selected section as a property of sectionsModel you can use a variable to set the initial value of it and declare the selectedValue observable in sectionsModel: http://jsfiddle.net/8djDw/6/. Let me know if you need any further help. – Flavia Obreja Feb 25 '14 at 11:07
  • I am really struggling with this, sorry. It seems to load the sections AJAx request twice. I have updated the code in the original request to better show where I am at. I really appreciate all the effort you have put into this. – Lex Eichner Feb 25 '14 at 11:23
  • Okay I have done that and I have put the wrapper and viewModel at the top of the script. It says userData isn't defined (Because on my script there isn't a userData variable it just uses the data which comes from the AJAX request. So I need to prevent it from updating the page before the AJAX requests are called (WHich are on click or on change. – Lex Eichner Feb 25 '14 at 11:32
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/48359/discussion-between-flavia-obreja-and-lex-eichner) – Flavia Obreja Feb 25 '14 at 11:33
0

As i understand, the sections are loaded based on the department selection. So why don't you just add the sectionList observableArray to the UserViewModel and just add a subscription to the department id, so everytime the department changes, the list of sections is loaded:

this.DeptId = ko.observable(data.DeptId);
this.sectionList = ko.observableArray(GetSections(this.DeptId()));
this.DeptId.subscribe(function () {
              var newSections =  GetSections(this.DeptId());
              this.sectionList(newSections);
            }, this);

GetSections function should call you GetSectionsByDept method. In your jsfiddle, if you comment the follwing lines, the data will load:

$("#ddbDepartments").val(data.DeptId);
$("#ddbDepartments").change();
$("#SectionsDDB").val(data.SectionId);

You should also replace the dropdownlist department with this:

<select name="DeptId" id="ddbDepartments" data-bind="options: DepartmentList, optionsValue: 'Id', optionsText: 'DeptName', value: DeptId"></select>

But make sure to have an array of departments objects with Id/DeptName properties.

Flavia Obreja
  • 1,227
  • 7
  • 13
  • Hi, thanks for your reply. The ddbdepartments loads on pageload and the data comes from the controller. So it needs to be in the foreach it currently is in. Yes your mostly correct. Clicking on a button (Of which there are many) Will call the ListUserLink AJAX method which then calls UserViewModel which, once the department value has been selected, will call SectionViewModel to show the sections. Once that is done you can then select a department which just calls the SectionViewModel. So I keep them seperate as the ListUserLink data doesn't contain the section info. Does that make sense? – Lex Eichner Feb 24 '14 at 16:20
  • Yes, but you will still have overlapping bindings because when you call ko.applyBindings(new UserViewModel(userdata)); it will also apply to SectionsDDB. Please check this link in order to see a way to handle it: http://www.knockmeout.net/2012/05/quick-tip-skip-binding.html. – Flavia Obreja Feb 24 '14 at 17:07
0

You doesn't need instance a new ViewModel always you have ajax server response. You can apply bindings when page is ready and update data changes when ajax is finished or you can just remove the viewmodel bindings and apply again in your ajax.

  • Thanks for your reply. How can I do it without the new viewmodel? – Lex Eichner Feb 24 '14 at 16:20
  • in your change event on success, replace "ko.applyBindings(new SectionViewModel(data), $(".SectionsDDB")[0]);" to "yourViewModelInstance.secionList(data.SectionList);" – Pedro Monte mor Feb 24 '14 at 16:25
  • Should I not declare the viewModel first? I have tried what you said by changing it to SectionViewModel.sectionList(data.SectionList); but that didn't work. Says SectionViewModel.sectionList is not a function which is what I would expect as it hasnt had a new instance declared. – Lex Eichner Feb 24 '14 at 16:32
  • @LexEichner, declare your viewModel inside your document ready event. Example: var yourInstance = new SectionViewModel(); and your ajax success you need say to your viewmodelInstace to update it's SectionList. Example: yourInstance.sectionList(data.SectionList); when you use this code line, your view will update the sectionList automatically – Pedro Monte mor Feb 24 '14 at 16:39
  • I like the idea, but it is still saying yourInstance is not defined. I will keep working on it tomorrow. – Lex Eichner Feb 24 '14 at 17:10