Search code examples
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:

    • Moved the "Add" button out of the table. This makes much more sense - it will be available when the list is empty and it will not be duplicated with every row.
    • Added an if binding to hide the entire qualifications table when there is nothing to show, and an ifnot binding to show a info paragraph when the list is empty.
    • Cleaned up the qualification list and removed duplication from your code.
    • It now stores the entire qualification object in the viewmodel, instead of just the ID. This is done by not using the optionsValue binding and saves you from having to duplicate the qualification details into each Qualification object. This also removes the need for an "Update" button.
    • Missing function deleteQualification added.
    • No inline "onkeypress" event handler in the HTML. It's not 1995 anymore, don't write inline event handlers. Knockout has the event binding for that.
    • Better object and property names. Only constructors are supposed to start with a capital letter.

    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>