Creating a CRUD Application Using Oracle JET

Using Knockout and the Oracle JET Common Model API, you can create applications that perform CRUD (Create, Read, Update, Delete) operations on data returned from a REST Service API.

Topics:

Note:

The application shown in this section also includes code for defining and displaying header and footer detail. You can download the complete sample application here: OracleJET-CommonModel-CRUD.zip.

Defining the ViewModel

Identify the data source for your application and create the ViewModel.

  1. Identify your data source and examine the data. For data originating from a REST service, identify the service URL and navigate to it in a browser.

    The following example shows a portion of the output of a REST service that returns department data for a fictitious organization from a REST server named RESTServer.

    {
      "Departments" : [ {
        "DepartmentId" : 10,
        "DepartmentName" : "Administration",
        "ManagerId" : null,
        "LocationId" : null,
        "version" : "ACED00057...contents truncated",
        "links" : {
          "self" : {
            "rel" : "self",
            "href" : "http://RESTServerIP:Port/stable/rest/Departments/10"
          },
          "canonical" : {
            "rel" : "canonical",
            "href" : "http://RESTServerIP:Port/stable/rest/Departments/10"
          },
          "Employees" : {
            "rel" : "child",
            "href" : "http://RESTServerIP:Port/stable/rest/Departments/10/Employees"
          }
        }
      }, {
        "DepartmentId" : 20,
        "DepartmentName" : "Retail Marketing",
        "ManagerId" : null,
        "LocationId" : null,
        "version" : "ACED00057...contents truncated",
        "links" : {
          "self" : {
            "rel" : "self",
            "href" : "http://RESTServerIP:Port/stable/rest/Departments/20"
          },
          "canonical" : {
            "rel" : "canonical",
            "href" : "http://RESTServerIP:Port/stable/rest/Departments/20"
          },
          "Employees" : {
            "rel" : "child",
            "href" : "http://RESTServerIP:Port/stable/rest/Departments/20/Employees"
          }
        }
      }, {
      ... contents omitted
      } ],
      "links" : {
        "self" : {
          "rel" : "self",
          "href" : "http://RESTServerIP:Port/stable/rest/Departments"
        }
      },
      "_contextInfo" : {
        "limit" : 25,
        "offset" : 0
      }
    }
    

    In this example, each department is identified by its DepartmentId and contains information about its name (DepartmentName), manager (ManagerId), and location (LocationId). Each department also contains employees (Employees) which are children of each department.

    Tip:

    The Oracle JET Common Model CRUD sample application uses JSON data returned from a mock rest service. You can find the mock rest service scripts in the public_html/js/rest folder. You can find the sample JSON data in the departments.json file located in the public_html/js folder.
  2. Determine the data you will need for your collection.

    For example, the following figure shows a simple Oracle JET table that uses the DepartmentId, DepartmentName, LocationId, and ManagerId returned from the REST service identified in the previous step to display a table of department IDs, names, location IDs, and manager IDs. The table element is defined as an ojTable component, which is included in the Oracle JET UI component library.

    The image is described in the surrounding text.
  3. Add a JavaScript function to your application that will contain your ViewModel.

    The following code shows a skeleton function that defines the ViewModel in the Oracle JET Common Model CRUD application. In this example, the function is stored in a file named app.js. The code to complete the ViewModel will be defined in upcoming steps.

    define(['ojs/ojcore', 'knockout', 'ojs/ojmodel'],
            function(oj, ko)
            {
                function viewModel() {
                // To be defined
                };
                return {'deptVM': viewModel};
            }
    )
    

    Note:

    This example uses RequireJS for modular development. The RequireJS bootstrap file will be shown in a later step. For additional information about using RequireJS, see Using RequireJS for Modular Development.

  4. Add a JavaScript function to the function you defined in the previous step that defines the data model using oj.Model.extend ().

    The highlighted code in the example below defines the data model for the application shown in the preceding figure. The Department variable represents a single record in the database and is displayed as a row in the table. Department is declared using the oj.Model.extend function call and instantiated in the declaration for myDept. The urlRoot property defines the data source, which in this case is the REST service URL.

    define(['ojs/ojcore', 'knockout', 'ojs/ojmodel'],
           function(oj, ko)
           {
               function viewModel() {
                   var self = this;
                   self.serviceURL = 'http://RESTServerIP:Port/stable/rest/Departments';
    
               parseDept = function(response) {
                   if (response['Departments']) {
                       var innerResponse = response['Departments'][0];
                       if (innerResponse.links.Employees == undefined) {
                           var empHref = '';
                       } else {
                           empHref = innerResponse.links.Employees.href;
                       }
                       return {DepartmentId: innerResponse['DepartmentId'],
                               DepartmentName: innerResponse['DepartmentName'],
                               links: {Employees: {rel: 'child', href: empHref}}};
                   }
                   return {DepartmentId: response['DepartmentId'],
                           DepartmentName: response['DepartmentName'],
                           LocationId:response['LocationId'],
                           ManagerId:response['LocationId'],
                           links: {Employees: {rel: 'child', href: response['links']['Employees'].href}}};
               };
    
               // Think of this as a single database record or a single table row.
                   var Department = oj.Model.extend({
                       urlRoot: self.serviceURL,
                       parse: parseDept,
                       idAttribute: 'DepartmentId'
                   });
        
                   var myDept = new Department();
               };
    
               return {'deptVM': viewModel};
           };
    }
    

    The parse property is an optional user callback function to allow parsing of JavaScript Object Notation (JSON) record objects as they are returned from the data service. In this example, parseDept is the callback function and simply maps the DepartmentId and DepartmentName returned from the REST service to DepartmentId and DepartmentName. If the LocationId or ManagerId records contain data, parseDept maps the attributes to LocationId and ManagerId.

    The parse callback can be useful for mapping database attribute names to names that may make more sense. For example, if your database uses Id and Name as the attributes that represent the department ID and department name, you could replace the return call in the parseDept function with:

    return {DepartmentId: response['Id'], DepartmentName: response['Name']};
    

    For a complete list of oj.Model properties and functions, see the oj.Model API documentation.

  5. Define the collection that will hold the data model object you defined in the previous step using oj.Collection.extend().

    The highlighted code in the example below defines the collection object for the Department model object. The DeptCollection variable is declared in the viewModel() function using the oj.Collection.extend function and instantiated in the declaration for self.DeptCol. The url property defines the data source, which in this case is the REST service URL, and limits the collection to 50 records.

    define(['ojs/ojcore', 'knockout', 'ojs/ojmodel'],
        function(oj, ko){
            function viewModel() {
                var self = this;
                self.serviceURL = 'http://RESTServerIP:Port/stable/rest/Departments';
                self.Departments = ko.observableArray([]);
                self.DeptCol = ko.observable();
                self.datasource = ko.observable();
    
                var parseDept = function(response) {
                ... contents omitted
                };
    
                var Department = oj.Model.extend({
                    urlRoot: self.serviceURL,
                    parse: parseDept,
                    idAttribute: 'DepartmentId'
                });
    
                var myDept = new Department();
    
                // this defines our collection and what models it will hold
                    var DeptCollection = oj.Collection.extend({
                        url: self.serviceURL + "?limit=50",
                        model: myDept
                    });
    
                    self.DeptCol(new DeptCollection());
                    self.datasource(new oj.CollectionTableDataSource(self.DeptCol()));
    
                }
                return {'deptVM': viewModel};
            }
    );
    

    Both the DeptCol and datasource objects are defined as Knockout observables so that changes to the data collection can be handled. The datasource object will contain the column data needed by the ojTable component and is passed the DeptCol observable as a parameter to oj.CollectionTableDataSource().

    The Departments object is defined as a Knockout observable array and will be populated in a later step.

    For a complete list of oj.Collection properties and functions, see the oj.Collection API documentation.

  6. Populate the collection with data by calling oj.Collection.fetch() to read the data from the data service URL.

    The highlighted code in the code sample below calls oj.collection.fetch() to add data to the DeptCol data collection and complete the ViewModel.

    define(['ojs/ojcore', 'knockout', 'ojs/ojmodel'],        function(oj, ko)        {
                function viewModel() {
                    var self = this;
                    self.serviceURL = 'http://RESTServerIP:Port/stable/rest/Departments';
                    self.Departments = ko.observableArray([]);
                    self.DeptCol = ko.observable();
                    self.datasource = ko.observable();
                    self.fetch = function(successCallBack) {
                        self.DeptCol().fetch({
                            success: successCallBack,
                            error: function(jqXHR, textStatus, errorThrown){
                                console.log('Error in fetch: ' + textStatus);
                            }
                        });
                    }
    
                    var parseDept = function(response) {
                       ... contents omitted
                    };
    
                    var Department = oj.Model.extend({
                        urlRoot: self.serviceURL,
                        parse: parseDept,
                        idAttribute: 'DepartmentId'
                    });
    
                    var myDept = new Department();
    
                    var DeptCollection = oj.Collection.extend({
                        url: self.serviceURL + "?limit=50",
                        model: myDept
                    });
    
                    self.DeptCol(new DeptCollection());
                    self.datasource(new oj.CollectionTableDataSource(self.DeptCol()));
    
                }
                return {'deptVM': viewModel};
            }
    );
    

    The fetch() function also defines an error callback that will log a message to the console if the fetch() call fails.

  7. Add the ViewModel or the file containing the name of your ViewModel to your RequireJS bootstrap file, typically main.js.

    If you created your Oracle JET application using an Oracle JET QuickStart template or modified your Oracle JET download as described in Using RequireJS to Manage Library, Link, and Script References, you should already have a main.js file. Locate the line that defines the require modules and add your file to the top of the list.

    For example, the code below lists the modules defined for the Common Model Sample. The application stores its ViewModel in the app.js file. The reference to the app.js file is highlighted in bold.

    require(['ojs/ojcore',
            'knockout', 
            'jquery', 
            'app', 
            'footer',
            'ojs/ojmodel', 
            'ojs/ojknockout', 
            'ojs/ojknockout-model', 
            'ojs/ojdialog',
            'ojs/ojinputtext',
            'ojs/ojinputnumber',
            'ojs/ojbutton',
            'ojs/ojtable',
            'ojs/ojdatacollection-common'], 
    

    You must also add the app reference in the callback definition as shown in the following example.

    // this callback gets executed when all required modules are loaded
    function(ko, $, oj, app, footer)
    {
        ...
    }
    
  8. Update your RequireJS bootstrap file to instantiate the ViewModel, create the Knockout bindings, and display the content on the page.

    The highlighted code in the code sample below creates a Knockout observable for each element in the deptData collection and assigns the resulting array to the Departments Knockout observable array you defined in a previous step.

    require(['ojs/ojcore',
            'knockout', 
            'jquery', 
            'app', 
            'footer',
            'ojs/ojmodel', 
            'ojs/ojknockout', 
            'ojs/ojknockout-model', 
            'ojs/ojdialog',
            'ojs/ojinputtext',
            'ojs/ojinputnumber',
            'ojs/ojbutton',
            'ojs/ojtable',
            'ojs/ojdatacollection-common'], 
            function(ko, $, oj, app, footer) // this callback gets executed when all required modules are loaded
            {
              var fvm = new footer.footerVM();
              $(document).ready(function(){
                var vm = new app.deptVM();
                ko.applyBindings(fvm, document.getElementById('footerContent'));
                vm.fetch(
                    function(collection, response, options){
                      var deptData = collection;
                      // This will create a ko.observable() for each element
                      // in the deptData response and assign the resulting array
                      // to the Departments ko observeableArray.
                      vm.Departments = oj.KnockoutUtils.map(deptData, null, true);
                      //perform a Knockout applyBindings() call binding this
                      // viewModel with the current DOM
                      ko.applyBindings(vm, document.getElementById('mainContent'));
                      //Show the content div after the REST call is completed.
                      $('#mainContent').show();
                });
              });
            }
    );
    

Reading Records

To read the records, define the elements that will read the records in your main HTML5 page and use Knockout to bind the elements to the data collection.

The following sample code shows a portion of the index.html file that displays a table of records using the ViewModel defined in the previous steps and the ojTable component. In this example, the mainContent div includes the table definition that creates Department Id, Department Name, Location Id, and Manager Id as the table header text and defines the content for each row.

<div id="mainContent" class="oj-md-12 oj-col page-padding" style="display: none;">
    <div class="demo-page-content-area page-padding">
        <table id="table"
               summary="Demo Table"
               data-bind="ojComponent:{component:'ojTable',
                          data: datasource,
                          columns: [
                                   {headerText: 'Department Id', field: 'DepartmentId', id: 'column1', sortable: 'enabled'},
                                   {headerText: 'Department Name', field: 'DepartmentName', id: 'column2', sortable: 'enabled'},
                                   {headerText: 'Location Id', field: 'LocationId', id: 'column3'},
                                   {headerText: 'Manager Id', field: 'ManagerId', id: 'column4'},
                                   ]}">
        </table>
    </div>
</div>

Knockout's data-bind utility binds the data retrieved from the REST service to the table elements.

Creating Records

To add the ability to create new records, add elements to your HTML5 page that accept input from the user and create a function that sends the new record to the REST server.

The figure below shows the result of adding the Form component to the Oracle Jet Common Model Sample application. The user can enter a new department number in the provided field or use the side arrows to increment or decrement the value. The user then enters the name and clicks Add.

The image is described in the surrounding text.

To add the ability to create new records to the application's ViewModel and HTML5 page:

  1. Add elements to the application's main page that accept input from the user.

    The highlighted code in the example below adds the form element to the index.html page shown in the previous task.

    <div id="mainContent" class="oj-md-12 oj-col demo-page-content-area page-padding" style="display: none;">
        <div class="page-padding">
            <div id="deptList" class="oj-md-9 oj-col">
                <table id="table" summary="Demo Table"
                    ... contents omitted
                </table>
                <br/>
            </div>
            <div id="addDept" class="oj-md-3 oj-col right">
                <div id="quickUpdate" class="frame">
                    <div id="newDeptForm">
                        <form class="oj-form oj-md-odd-cols-6 oj-md-labels-inline" data-bind="submit: addDepartment">
                            <h3>New Department</h3><hr/>
                            <div class="oj-row">
                                <div class="oj-col">
                                    <label for="newDepartId" class="oj-label">Department Id</label>
                                </div>
                                <div class="oj-col">
                                    <input id="newDepartId" value="555" class="oj-text-input" data-bind="ojComponent:{component:'ojInputNumber'}" />
                                </div>
                            </div>
                            <div class="oj-row">
                                <div class="oj-col">
                                    <label for="newDepartName" class="oj-label">Department Name</label>
                                </div>
                                <div class="oj-col">
                                    <input id="newDepartName" maxlength='30'
                                           placeholder="enter new name" data-bind="ojComponent:{component:'ojInputText'}"/>
                                    <button id="saveBtn" class="oj-button"
                                            type="submit" data-bind="ojComponent:{component:'ojButton', label: 'Add Department'}">
                                    </button>
                                </div>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
    

    The form is defined to contain two input fields: an ojInputNumber for the department ID, and an ojInputText for the department name. The ojButton component is used for the form's button.

    The form's submit action is bound to the addDepartment function which is defined in the next step.

  2. Add code to the ViewModel to add the user's input as a new record (model) in the data collection.

    The highlighted code in the example below shows the addDepartment() function that adds the new department number and department name to the DeptCol data collection. In this example, the function calls the oj.Collection.create() method which creates the new model instance, adds it to the data collection, and saves the new DepartmentId and DepartmentName to the data service.

    define(['ojs/ojcore', 'knockout', 'ojs/ojmodel'],
             function(oj, ko)        {
                function viewModel() {
                    var self = this;
                    self.serviceURL = 'http://RESTServerIP:Port/stable/rest/Departments';
                    self.Departments = ko.observableArray([]);
                    self.DeptCol = ko.observable();
                    self.datasource = ko.observable();
                    self.fetch = function(successCallBack) {
                        ... contents omitted
                    };
    
                    function parseDept(response){
                        ... contents omitted
                    };
    
                    var Department = oj.Model.extend({
                        urlRoot: self.serviceURL,
                        parse: parseDept,
                        parseSave:parseSaveDept,
                        idAttribute: 'DepartmentId'
                    });
    
                    var myDept = new Department();
    
                    var DeptCollection = oj.Collection.extend({
                        url: self.serviceURL + "?limit=50",
                        model: myDept
                        comparator: 'DepartmentId'
                    });
    
                    self.DeptCol(new DeptCollection());
                    self.datasource(new oj.CollectionTableDataSource(self.DeptCol()));
    
                    function parseSaveDept(response){
                        return {Departments: [{DepartmentId: response['DepartmentId'],
                                DepartmentName: response['DepartmentName'],
                                LocationId:response['LocationId'],
                                ManagerId:response['LocationId'],
                                links: {Employees: {rel: 'child', href: response['links']['Employees'].href}}}]};
                    };
    
                    self.addDepartment = function(formElement, event){
                        var recordAttrs = {DepartmentId: formElement.elements[0].value,
                            DepartmentName: formElement.elements[1].value,
                            ManagerId: "", LocationId: "",
                            links: {Employees: {rel: 'child', href: 'http://RESTServerIP:Port/stable/rest/Departments/' + formElement.elements[0].value + '/Employees'}}};
                        this.DeptCol().create(recordAttrs,{
                            'contentType': 'application/vnd.oracle.adf.resource+json',
                            error: function(jqXHR, textStatus, errorThrown){
                                 console.log('Error in Create: ' + textStatus);
                            }
                        });
                    }
                }
                return {'deptVM': viewModel};
            }
    );
    

    The oj.Collection.create() function accepts options to control the record save. In this example, simple error checking is added. In addition, the create() function sends a contentType to the REST server. Depending upon the REST service you are using, this option may not be required or may need modification.

Updating Records

To add the ability to update records, add elements to your HTML5 page that accept input from the user and create a function that sends the updated record to the REST server.

The figure below shows the Oracle JET Common Model Sample application configured to allow updates to the department name. When the user moves the focus over a department name, a tooltip appears that prompts the user to click to edit. department name, a tooltip prompts the user to click to edit. If the user clicks the department name, a dialog appears that enables the user to change the name. The user can type in a new department name and click Change to update the record or Cancel to keep the existing department name.

The image is described in the surrounding text.

To add the ability to update records to the application's ViewModel and HTML5 page:

  1. Add elements to the application's main page that identifies updatable elements and enables the user to perform an action to update them.

    The highlighted code in the example below adds the ojDialog component to the page and provides the prompt to the user to click to edit the page.

    <div id="mainContent" class="oj-md-12 oj-col page-padding" style="display: none;">
        <div class="demo-page-content-area page-padding">
            <table id="table"
                   summary="Demo Table"
                   data-bind="ojComponent:{component:'ojTable',
                             data: datasource,
                             selectionMode: {row: 'none', column:'none'},
                             columns: [
                                      {headerText: 'Department Id', field: 'DepartmentId', id: 'column2', sortable: 'enabled'},
                                      {headerText: 'Department Name', field: 'DepartmentName', id: 'column3', sortable: 'enabled'},
                                      {headerText: 'Location Id', field: 'LocationId', id: 'column3'},
                                      {headerText: 'Manager Id', field: 'ManagerId', id: 'column4'}
                                      ],
                             rowTemplate: {template: 'row_tmpl'}}">
            </table>
        </div>
        <div id="editDialog" title="Change Department Name"
             data-bind="ojComponent:{component: 'ojDialog', initialVisibility: 'hide', resizeBehavior: 'none', dragAffordance: 'title-bar', modality: 'modeless'}">
            <form class="oj-form " data-bind="submit: updateDeptName">
                <div class="oj-dialog-header oj-helper-clearfix" aria-labelledby="dialog-title-id">
                    <div>
                        <span id="infoIcon" class="oj-icon oj-message-info-icon" style="float:left;"></span>
                        <span id="dialog-title-id" class="oj-dialog-title">Change Department Name</span>
                        <a href="#"><span  title="click to close dialog"  role="img" class="oj-clickable-icon oj-fwk-icon oj-fwk-icon-cross" style="float:right;" data-bind="click: function(){$('#editDialog').ojDialog('close');}"></span></a>
                    </div>
                </div>
                <div class="oj-dialog-body">
                    <div class="oj-md-odd-cols-4">
                        <label for="newName" class="oj-label oj-label-inline">Department Name</label>
                        <input id="newName" type="text" data-bind="ojComponent: {component:'ojInputText', value: currentDeptName()}">
                    </div>
                </div>
                <div class="oj-dialog-footer">
                    <button id="submitBtn" type="submit" data-bind="ojComponent: {component:'ojButton', label: 'Change'}"></button>
                    <button id="resetBtn" data-bind="ojComponent: {component: 'ojButton', label: 'Cancel'}, click: function(){$('#editDialog').ojDialog('close');}"></button>
                </div>
            </form>
        </div>
    </div>
    <script type="text/html" id="row_tmpl">
        <tr>
            <td><span id='deptId' data-bind="text: DepartmentId"></span></td>
            <td><div id="deptName" title='Click to edit' data-bind="text: DepartmentName, click: function(data, event){$root.showChangeNameDialog(DepartmentId,data,event)}"></div></td>
            <td><div id="locId" data-bind="text: LocationId"></div></td>
            <td><div id="mgrId" data-bind="text: ManagerId"></div></td>
        </tr>
    </script>
    

    The ojDialog component includes the oj-dialog-header, oj-dialog-body, and oj-dialog-footer formatting classes. The oj-dialog-header class is optional and is used in this example to format the Info icon, title, and Close icon.

    The oj-dialog-body class formats the dialog body which includes the Department Name label and an ojInputText component to capture the user's input. The footer defines two ojButton components which add the Change and Cancel buttons to the dialog. When the user clicks Change, the updateDepartmentName() function handles the updates to the record.

    The original table is also modified to define the rowTemplate property which specifies the row_tmpl template to use for the row display. The template script adds the tooltip (Click to Edit) and calls the showChangeNameDialog() function to display the dialog.

    The updateDepartmentName() and showChangeNameDialog() functions are defined in the next step. For additional information about the ojDialog component, see the ojDialog API documentation.

  2. Add code to the ViewModel to update the record.

    The highlighted code in the example below shows the updateDepartment() and showChangeNameDialog() functions.

    define(['ojs/ojcore', 'knockout', 'ojs/ojmodel'],
             function(oj, ko)        {
                function viewModel() {
                    var self = this;
                    self.serviceURL = 'http://RESTServerIP:Port/stable/rest/Departments';
                    self.Departments = ko.observableArray([]);
                    self.currentDeptName = ko.observable('default');
                    self.workingId = ko.observable('');
                    self.DeptCol = ko.observable();
                    self.datasource = ko.observable();
                    self.fetch = function(successCallBack) {
                        ... contents omitted
                    };
    
                    function parseDept(response){
                        ... contents omitted
                    }
    
                    var Department = oj.Model.extend({
                        urlRoot: self.serviceURL,
                        parse: parseDept,
                        parseSave:parseSaveDept,
                        idAttribute: 'DepartmentId'
                    });
    
                    var myDept = new Department();
    
                    var DeptCollection = oj.Collection.extend({
                        url: self.serviceURL + "?limit=50",
                        model: myDept
                        comparator: 'DepartmentId'
                    });
    
                    self.DeptCol(new DeptCollection());
                    self.datasource(new oj.CollectionTableDataSource(self.DeptCol()));
    
                    self.showChangeNameDialog = function(deptId, data, event) {
                        var currName = data.DepartmentName;
                        self.workingId(deptId);
                        self.currentDeptName(currName);
                        $('#editDialog').ojDialog('open');
                    }
     
                    self.updateDeptName = function(formData, event) {
                        var currentId = self.workingId();
                        var newName = formData.elements[0].value;
                        if (newName != self.currentDeptName() && newName != '') {
                            var myCollection = self.DeptCol();
                            var myModel = myCollection.get(currentId);
                            myModel.save({'DepartmentName': newName}, {
                                contentType: 'application/vnd.oracle.adf.resource+json',
                                success: function(myModel, response, options) {
                                    $('#editDialog').ojDialog('close');
                                },
                                error: function(jqXHR, textStatus, errorThrown) {
                                    alert("Update failed with: " + textStatus);
                                    $('#editDialog').ojDialog('close');
                                }
                            });
                        } else {
                            alert('Department Name is not different or the new name is not valid');
                            $('#editDialog').ojDialog('close');
                        }
                    }
    
                    function parseSaveDept(response){
                        ... contents omitted
                    }
    
                }
                return {'deptVM': viewModel};
            }
    );
    

    The showChangeNameDialog() function stores the selected department detail and opens the dialog with the existing department name shown in the ojInputText field.

    The updateDepartment() function calls the oj.Model.save() method to save the current Model object to the data source. The function also defines success and error callbacks to close the dialog upon success or issue an error message if the record was not updated.

Deleting Records

To add the ability to delete records, add elements to your HTML5 page that accept input from the user and create a function that sends the new record for deletion to the REST server.

The figure below shows the Oracle Jet Common Model CRUD application configured to allow record deletion. The user can check one or more departments in the list and click Remove Department to delete the record or records.

The image is described in the surrounding text.

To add the ability to delete records to the application's ViewModel and HTML5 page:

  1. Add elements to the application's main page that identifies records marked for deletion and enables the user to perform an action to delete them.

    The highlighted code in the example below adds the Remove column with a check box to the department list and adds the Remove Department button below the list.

    <div id="mainContent" class="oj-md-12 oj-col page-padding" style="display: none;">
        <div class="demo-page-content-area page-padding">
            <table id="table"
                   summary="Demo Table"
                   data-bind="ojComponent:{component:'ojTable',
                             data: datasource,
                             selectionMode: {row: 'none', column:'none'},
                             columns: [
                                      {headerText: 'Remove', id: 'column1', sortable: 'disabled'},
                                      {headerText: 'Department Id', field: 'DepartmentId', id: 'column2', sortable: 'enabled'},
                                      {headerText: 'Department Name', field: 'DepartmentName', id: 'column3', sortable: 'enabled'},
                                      {headerText: 'Location Id', field: 'LocationId', id: 'column3'},
                                      {headerText: 'Manager Id', field: 'ManagerId', id: 'column4'}
                                      ],
                             rowTemplate: {template: 'row_tmpl'}}">
            </table>
            <br/><br/>
            <button id="deleteDept_btn"
                    data-bind="enable: somethingChecked, click: deleteDepartment">Remove Department
            </button>
        </div>
    </div>
    
    <script type="text/html" id="row_tmpl">
        <tr>
            <td><input type="checkbox" data-bind="attr: {id: DepartmentId}, click: $root.enableDelete"/></td>
            <td><div id='deptId' data-bind="text: DepartmentId"></div></td>
            <td><div id="deptName" data-bind="text: DepartmentName"></div></td>
            <td><div id="locId" data-bind="text: LocationId"></div></td>
            <td><div id="mgrId" data-bind="text: ManagerId"></div></td>
        </tr>
    </script>
    

    The original table is modified to include the Remove column. The rowTemplate property specifies the row_tmpl template to use for the row display. The template script adds the checkbox input element to the first column and the value of DepartmentId, DepartmentName, and LocationId to the remaining columns.

    The button's click action is bound to the deleteDepartment function which is created in the next step.

  2. Add code to the ViewModel to delete the record or records submitted by the user.

    The highlighted code in the example below shows the deleteDepartment() function. In this example, the function calls the oj.Collection.remove() method which removes the model or models from the data collection. To delete the record from the data source, the function calls the oj.Model.destroy() method.

    define(['ojs/ojcore', 'knockout', 'ojs/ojmodel'],
             function(oj, ko)         {
                 function viewModel() {
                    var self = this;
                    self.serviceURL = 'http://RESTServerIP:Port/stable/rest/Departments';
                    self.Departments = ko.observableArray([]);
                    self.DeptCol = ko.observable();
                    self.datasource = ko.observable();
                    self.somethingChecked = ko.observable(false);
                    self.fetch = function(successCallBack) {
                        ... contents omitted
                    }
    
                    var Department = oj.Model.extend({
                        urlRoot: self.serviceURL,
                        idAttribute: 'DepartmentId'
                    });
    
                    var myDept = new Department();
    
                    var DeptCollection = oj.Collection.extend({
                        url: self.serviceURL + "?limit=50",
                        model: myDept
                        comparator: 'DepartmentId'
                    });
    
                    self.DeptCol(new DeptCollection());
                    self.datasource(new oj.CollectionTableDataSource(self.DeptCol()));
    
                    self.enableDelete = function() {
                        if (!$('input[type=checkbox]:checked').length) {
                            self.somethingChecked(false);
                        } else {
                            self.somethingChecked(true);
                        }
                        return true;
                    }
     
                    self.deleteDepartment = function(data, event) {
                        var deptIds = [];
                        deptIds = self.findDeptIds();
                        var collection = data.DeptCol();
                        deptIds.forEach(function(value, index, arr) {
                            var model = collection.get(parseInt(value));
                            if (model) {
                                collection.remove(model);
                                model.destroy();
                            }
                        });
                        self.enableDelete();
                        $('#table').ojTable('refresh');
                    }
     
                    self.findDeptIds = function() {
                        var selectedIdsArray = [];
                        $("input:checkbox").each(function() {
                            var $this = $(this);
                            if ($this.is(":checked")) {
                                selectedIdsArray.push($this.attr("id"));
                            }
                        });
                        return selectedIdsArray;
                    }
                }
                return {'deptVM': viewModel};
            }
    );
    

    The deleteDepartment() function calls the findDeptIds() function which returns the list of selected departments marked for deletion. The enableDelete() function resets the check box after the department list is deleted.