knockout.jsknockout-3.0

Dynamic Add New Row Using knockout js


using knockoutjs, i have 2 column Qualification List and Marks . After i click Add button , i want to generate new rows and (Add) button change to Update Button.

Here is my demo: https://jsfiddle.net/fjbrsvgn/3/

function Qualification(data) {
var self = this;
self.QualId = ko.observable(data.QualId);
self.QualName = ko.observable(data.QualName);
self.Marks = ko.observable(data.Marks);
}
function QualificationList(data) {
var self = this;
self.QualId = ko.observable(data.QualId);
self.QualName = ko.observable(data.QualName);
}
var QualificationViewModel = function () {
var self = this;
self.Marks = ko.observable();
self.Qualifications = ko.observableArray(Qualification);
self.QualificationLists = ko.observableArray([
    { QualName: 'Master', QualId: '0' },
    { QualName: 'Bachelor', QualId: '1' },
    { QualName: 'CA', QualId: '2' },
    { QualName: 'School Leaving', QualId: '3' }
]);
self.selectedQualName = ko.observable();
self.AddQualification = function () {
    self.Qualifications.push({
        QualList: "",
        QualificationLists: "",
        Marks: "",
        selectedQualName: "",
    });
};
self.SaveQualification = function () {
    console.log(self.Qualifications());
};
};



$(document).ready(function () {
var qualificationViewModel = new QualificationViewModel();
ko.applyBindings(qualificationViewModel);
});

it shows Error: The argument passed when initializing an observable array must be an array, or null, or undefined. My expected result when i console Qualifications need to show Qualification Name Qualification Id and Marks.


Solution

  • The error says

    The argument passed when initializing an observable array must be an array, or null, or undefined.

    and you are doing this:

    self.Qualifications = ko.observableArray(Qualification);
    

    This passes a function to the observable array. This cannot work. You probably wanted to make single new qualification as the default value of qualifications.

    self.Qualifications = ko.observableArray([new Qualification()]);
    

    However, I would initialize the list as empty and let the user add something only when there is something to add. This saves screen space.

    The following is an improved version of your attempt:

    function checkKeyIsDigit(vm, event) {
      return event.charCode >= 48 && event.charCode <= 57 || event.charCode === 46;
    }
    
    function Qualification(data) {
      var self = this;
      self.qual = ko.observable();
      self.marks = ko.observable();
    }
    
    function EmployeeQualification() {
      var self = this;
      self.qualificationList = ko.observableArray([
        {id: '0', name: 'Master'},
        {id: '1', name: 'Bachelor'},
        {id: '2', name: 'CA'},
        {id: '3', name: 'School Leaving'}
      ]);
      self.qualifications = ko.observableArray();
      self.addQualification = function() {
        self.qualifications.push(new Qualification());
      };
      self.deleteQualification = function(qual) {
        self.qualifications.remove(qual);
      };
      self.saveQualification = function() {
        console.log(self.qualifications());
      };
    }
    
    var vm = new EmployeeQualification();
    ko.applyBindings(vm);
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
    
    <h3>Employee Qualification</h3>
    <hr>
    
    <div class="col-md-12">
      <p data-bind="ifnot: qualifications().length">No qualifications</p>
      <table class="table-bordered" data-bind="if: qualifications().length">
        <thead>
          <tr>
            <th class="text-center">Qualification</th>
            <th class="text-center">Marks</th>
            <th class="text-center">Action</th>
          </tr>
        </thead>
        <tbody data-bind="foreach: qualifications">
          <tr>
            <td>
              <select class="form-control" data-bind="
                value: qual,
                options: $parent.qualificationList,
                optionsText: 'name',
                optionsCaption: '--Choose--'
            "></select>
            </td>
            <td>
              <input type="text" placeholder='Marks' data-bind="
                value: marks,
                event: {keypress: checkKeyIsDigit}
              " class="form-control">
            </td>
            <td>
              <button class="btn btn-default" data-bind="click: $parent.deleteQualification">Delete</button>
            </td>
          </tr>
        </tbody>
      </table>
      <hr>
      <div class="col-md-6">
        <button class="btn btn-default" data-bind="click: addQualification">Add Qualification</button>
        <button class="btn btn-default" data-bind="click: saveQualification">Submit</button>
      </div>
    </div>
    <hr>
    <pre data-bind="text: ko.toJSON($root, null, 2)"></pre>