Search code examples
javascriptarraysasp.net-mvcknockout.jsknockout-mapping-plugin

Add and remove Items on client side with Knockoutjs


I've been struggling to make an interactive form, in which a viewmodel has a collection of items. I want to dynamically add/remove items from that collection.

I've found it difficult to find examples that go to this depth and most of them usually stay on a more straight forward implementation, however I've come across This post which pretty much explains what i'm doing with this brilliant jsfiddle, in which a json is pulled using the knockout mapping pluggin and then mapped.

var company;

function PersonViewModel(data) {
  var personMapping = {
    'ignore': ['twitter', 'webpage'],
    'copy': ['age'],
    'lastName': {
      'create': function (options) {
        return ko.observable(options.data.toUpperCase());
      }
    }
  };

  ko.mapping.fromJS(data, personMapping, this);

  this.fullName = ko.computed(function () {
    return this.firstName() + ' ' + this.lastName();
  }, this);
}

function CompanyViewModel(data) {
  var companyMapping = {
    'ignore': ['address', 'website'],
    'name': {
      'create': function (options) {
        return ko.observable(options.data.toUpperCase());
      }
    },
    'employees': {
      key: function (data) {
        return ko.utils.unwrapObservable(data.personId);
      },
      create: function (options) {
        return new PersonViewModel(options.data);
      }
    }
  };

  ko.mapping.fromJS(data, companyMapping, this);
}

What i don't know how to achieve is how and where exactly to add the 'addEmployee' and 'removeEmployee' functions? and how to bind them to a button?.

Thank you in advance!


Solution

  • The logical place to add these would be to your CompanyViewModel. For example, something like this:

    function CompanyViewModel(data) {
      var self = this;
      var companyMapping = {
         // ...as before
      };
    
      self.addEmployee = function () {
        // as an example, we are just adding a static new employee
        self.employees.push(new PersonViewModel({
          lastName: "new",
          firstName: "employee",
          age: 10
        }));
      }
    
      // important, with how we are binding the function, we expect the 
      // argument, e, to be the employee to remove
      self.removeEmployee = function (e) {
        self.employees.remove(e);
      }
    
      ko.mapping.fromJS(data, companyMapping, this);
    }
    

    Add to bind, you can do something like this:

    <div id="company">
      <h1 data-bind="text: name"></h1>
      <h2>Employees</h2>
      <input type="button" value="add" data-bind="click: addEmployee" />
      <table>
        <thead>
          <tr>
            <th>Full name</th>
            <th>Last name</th>
            <th>First name</th>
            <th>Age</th>
          </tr>
        </thead>
        <tbody data-bind="foreach: employees">
          <tr>
            <td data-bind="text: fullName"></td>
            <td data-bind="text: lastName"></td>
            <td data-bind="text: firstName"></td>
            <td data-bind="text: age"></td>
            <td>
              <input type="button" value="x" data-bind="click: $parent.removeEmployee" />
            </td>
          </tr>
        </tbody>
      </table>
    </div>
    

    Which will add an add button as well as a remove x button to each employee which calls the removeEmployee function on the parent CompanyViewModel passing in the current employee.

    Here's an updated fiddle