Search code examples
javascriptjsonknockout.jsknockout-mapping-plugin

Load observables from inside a model in Knockout.js


I have a model for a projects view which is very simple. There is a click handler for editing a projects which should load the project data from the server into the model. The click handler is called editProject. All I want is to load my observables from the server. This method from inside the models did not work.

this.editProject = function(){
    $.getJSON("/projects/get_by_id", {
        id: "7"
    }).done(function(data){
        ko.mapping.fromJSON(data[0], self);
    });
};

Solution

  • I'm not sure what went wrong because you haven't shown your view or whole view model. How are you initializing your viewmodel before editProject is called?

    Here's an example with the editProject method in a layer above the actual project.

    • Each project has its own viewmodel, created by calling ko.mapping.toJS on a plain object (from the server)
    • The initial object already has all properties, but some without values.
    • In the success callback of the ajax request, the clicked project is updated by passing it as a second parameter: ko.mapping.fromJS(data, project).

    If data contains new properties, those won't be available at the time of ko.applyBindings, and therefore won't be data-bound.

    const $ = fakeJQuery();
    
    // The project should have its own viewmodel with initial data
    // probably received from the server during first load
    const myProjects =[
      ko.mapping.fromJS({ id: "1", additionalInfo: null }),
      ko.mapping.fromJS({ id: "7", additionalInfo: null })
    ];
    
    const ViewModel = function() {
      this.projects = ko.observableArray(
        myProjects
      );
    
      // Take the project to edit as an argument
      this.editProject = function(project) {
        $.getJSON("/projects/get_by_id", project)
          .done(function(data) {
            // Because we have access to the clicked project,
            // we map to its existing viewmodel using `fromJS`
            ko.mapping.fromJS(data, project);
          });
      };
    };
    
    ko.applyBindings(new ViewModel());
    
    // Mocks
    function fakeJQuery() {
      return {
        // Mock ajax request
        getJSON: function(url, opts) {
          let done = () => true;
    
          setTimeout(function() {
            // Test data to indicate an update:
            done({
              id: opts.id(),
              additionalInfo: "Test " + opts.id()
            });
          }, 1000);
    
          return {
            done: cb => done = cb
          };
        }
      };
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
    
    <ul data-bind="foreach: projects">
      <li>
        <code data-bind="text: 'id: ' + id()"></code>
        <button data-bind="click: $parent.editProject">edit</button>
    
        <p>
          Info:
          <strong data-bind="text: additionalInfo"></strong>
          <em data-bind="visible: !additionalInfo()">not loaded</em>
        </p>
      </li>
    </ul>