Handle selection events in an Oracle JET web app

Introduction

You can use the Oracle JavaScript Extension Toolkit (Oracle JET) API in the viewModel of the Oracle JET web app to create event handlers that respond to the Oracle JET List View component selected attribute change listener. When the user selects or deselects an item in the list, the change listener triggers your event handler. You can use the event handler to populate the Oracle JET data provider, and you can bind the data provider to observables that the view component can use. The event handler can also set the boolean variable that the Oracle JET If Binding component uses to conditionally render its nested HTML elements in the view. If the selected attribute of the Oracle JET List View component for the master list isn’t empty, then the variable is set to true, and the view renders the databound detail list. If the selected attribute is empty due to a deselection event, then the variable is set to false, and the view renders a container in the view without the databound detail list.

Objectives

In this tutorial, you will update the user interface of an Oracle JET web app so that you can display master-detail data. You learn how to create a JavaScript event handler by using an Oracle JET property change listener. You also learn how to use an Oracle JET If Binding component to conditionally display the detail list to handle list selections.

Prerequisites

Task 1: Set Selection Behavior in the View

Update the view to customize the List View components to handle a row selection in the Activities list and Activity Items list. The Oracle JET List View component defines the first-selected-item attribute that the component populates with the data items of the user’s row selection and that you can read into an observable by using a two-way binding. Use the component’s on-selection-changed attribute to catch and process list selection/deselection events by binding an event listener that you define in the viewModel.

  1. Navigate to the JET_Web_Application/src/ts/views directory and open the dashboard.html file in an editor.

  2. Below the Activities heading, find the oj-list-view custom HTML element where id="activitiesList", and then add the selection behavior attributes after the gridlines.item attribute.

       
    <h3 id="activitiesHeader">Activities</h3>
       <oj-list-view id="activitiesList" class="item-display" aria-labelledby="activitiesHeader"
         data="[[activityDataProvider]]" gridlines.item="visible" selection-mode="single" selected="{{selectedActivity}}"
         on-first-selected-item-changed="[[selectedActivityChanged]]" first-selected-item="{{firstSelectedActivity}}"
         scroll-policy="loadMoreOnScroll" scroll-policy-options.fetch-size="5">
         <template slot="itemTemplate">
    . . .
       
    
  3. Below the Activity Items heading, find the oj-list-view custom HTML element where id="itemsList", and then add the selection behavior attributes after the gridlines.item attribute.

       
    <h3 id="itemsListHeader">Activity Items</h3>
         <oj-list-view id="itemsList" class="item-display" data="[[itemsDataProvider]]" aria-labelledby="itemsListHeader"
           gridlines.item="visible" selection-mode="single" selected="{{selectedItem}}"
           on-first-selected-item-changed="[[selectedItemChanged]]" first-selected-item="{{firstSelectedItem}}"
           scroll-policy="loadMoreOnScroll" scroll-policy-options.fetch-size="5">
           <template slot="itemTemplate">
    . . .
       
    
  4. Save the dashboard.html file.

    Your file should look similar to event-task1-dashboard-html.txt.

Task 2: Create Event Handlers in the ViewModel

Update the viewModel to add event handlers for the List View components to respond to selections in the Activities list and the Activity Items list. The Oracle JET List View component defines a selected attribute, on which the Oracle JET List View API defines a property change listener. Your event handler sets the selectedActivity and selectedItem observables when the user makes a list selection and the value of the selected attribute changes.

  1. Navigate to the JET_Web_Application/src/ts/viewModels directory and open the dashboard.ts file in an editor.

  2. Below the pieSeriesValue observable definition, before the constructor() method, add observables for the Activities List selection and the Activity Items list selection.

    class DashboardViewModel {
       . . . 
       pieSeriesValue: ko.ObservableArray;
       // Observables for Activities
       selectedActivity = new ObservableKeySet();
       activitySelected = ko.observable(false);  // Controls display of Activity Items
       firstSelectedActivity = ko.observable();
       selectedActivityIds = ko.observable();
       
       // Observables for Activity Items
       itemSelected = ko.observable(false);
       selectedItem = ko.observable();
       firstSelectedItem = ko.observable();
       
       constructor() {
       . . .
       
    
  3. At the top of the dashboard.ts file, import the ObservableKeySet class from the ojs/ojknockout-keyset module and the ojListView class from the ojs/ojlistview module.

    import * as ko from "knockout";
    . . . 
    import "ojs/ojavatar";
    import { ObservableKeySet } from "ojs/ojknockout-keyset";
    import { ojListView } from "ojs/ojlistview";
    
  4. Before the DashboardViewModel class, add the ActivityItems type alias.

    . . . 
    type ActivityItems = {
       id: number;
       name: string;
       items: Array<Item>;
       short_desc: string;
       image: string;
    };
       
    class DashboardViewModel {
    . . .
    
  5. After the DashboardViewModel class constructor() method declaration, add the selectedActivityChanged event handler with test conditions to handle the selection and deselection events.

    } // End of constructor function
       
    selectedActivityChanged = (event: ojListView.firstSelectedItemChanged<ActivityItems["id"], ActivityItems>) => {
       /**
       *  If no items are selected then the firstSelectedItem property  returns an object 
       *  with both key and data properties set to null.
       */
       let itemContext = event.detail.value.data;
       
       if (itemContext != null) {    
          // If selection, populate and display list
       
       } else {
             // If deselection, hide list          
       }
    };
    

    Implement this event handler in the next steps.

  6. Within the if statement of the selectedActivityChanged event handler, populate the itemsDataProvider observable by using the itemsArray variable, and then set the activitySelected and itemSelected selection state observables to true for the selection event.

       
    selectedActivityChanged = (event: ojListView.firstSelectedItemChanged<ActivityItems["id"], ActivityItems>) => {
       /**
        *  If no items are selected, then this property firstSelectedItem 
        *  will return an object with both key and data properties set to null.
       */
       let itemContext = event.detail.value.data;
       
       if (itemContext != null) {    
          // If selection, populate and display list
          // Hide currently-selected activity item
          this.activitySelected(false);
       
          let itemsArray = itemContext.items;
          this.itemsDataProvider.data = itemsArray;
          // Set List View properties
          this.activitySelected(true);
          this.itemSelected(false);
          this.selectedItem();
          this.itemData();
       
       } else {
             // If deselection, hide list
       }
    };
       
    
  7. Within the else statement of the selectedActivityChanged event handler, set the activitySelected and itemSelected selection state observables to false for the deselection event.

       
    selectedActivityChanged = (event: ojListView.firstSelectedItemChanged<ActivityItems["id"], ActivityItems>) => {
       /**
        *  If no items are selected then this property firstSelectedItem will return an 
        *  object with both key and data properties set to null.
       */
       
       let itemContext = event.detail.value.data;
       
       if (itemContext != null) {    
          . . .
       } 
       
       else {
          // If deselection, hide list
          this.activitySelected(false);
          this.itemSelected(false);
       }
    };
       
    
  8. After the selectedActivityChanged event handler, add a selectedItemChanged event handler with test conditions to handle the selection and deselection events.

       
    selectedActivityChanged = (event: ojListView.firstSelectedItemChanged<ActivityItems["id"], ActivityItems>) => {
       . . .
    };
       
    /**
    * Handle selection from Activity Items list
    */
    selectedItemChanged = (event: ojListView.firstSelectedItemChanged<Item["id"], Item>) => {
       
       let isClicked = event.detail.value.data;
       
       if (isClicked != null) {
       
           // If selection, populate and display list
       }
       else {
       // If deselection, hide list
       }
    };
    

    Implement this event handler in the next steps.

  9. Within the if statement of the selectedItemChanged event handler, populate the itemData observable, populate the pieSeriesValue observable by using the pieSeries array variable, and then set the itemSelected selection state observable to true for the selection event.

    selectedItemChanged = (event: ojListView.firstSelectedItemChanged<Item["id"], Item>) => {
       
       let isClicked = event.detail.value.data;
       
       if (isClicked != null) {
       
          // If selection, populate and display list
          this.itemData(event.detail.value.data);
       
          // Create variable and get attributes of the items list to set pie chart values
          let pieSeries = [
          { name: "Quantity in Stock", items: [this.itemData().quantity_instock] },
          { name: "Quantity Shipped", items: [this.itemData().quantity_shipped] }
          ];
          // Update the pie chart with the data
          this.pieSeriesValue(pieSeries);
       
          this.itemSelected(true);
       
       }
       else {
       // If deselection, hide list
       
       }
    };
    
  10. Within the else statement of the selectedItemChanged event handler, set the itemSelected selection state observable to false for the deselection event.

    selectedItemChanged = (event: ojListView.firstSelectedItemChanged<Item["id"], Item>) => {
        
       if (isClicked != null) {
       . . . 
       }
       else {
       // If deselection, hide list
       this.itemSelected(false);        
       }
    };
    
  11. Save the dashboard.ts file.

    Your file should look similar to final-event-dashboard-ts.txt.

Task 3: Conditionalize List Rendering in the View

Update the view using Oracle JET If Binding components to conditionally render the detail list. The Oracle JET If Binding component takes a boolean variable on its test attribute. Within one If Binding component, nest the Activity Items container and, in another If Binding component, nest the Item Details container. Then use the If Binding components to test the state of the observables activitySelected and itemSelected. The nested content of your If Binding component renders if the test condition is true. Display the databound list if the observable is true, as set by the list event handler. Use another If Binding component to test if the observable is false, and then display a container with a message directing the user to make a list selection.

  1. Navigate to the JET_Web_Application/src/ts/views directory and open the dashboard.html file in an editor.

  2. Find the div element where id="parentContainer2". Above it, add the opening tag of the oj-bind-if custom HTML element, with the test attribute set to the state of the activitySelected observable.

    . . .
       </oj-list-view>
    </div>
    <oj-bind-if test="[[activitySelected()]]">
    <div id="parentContainer2" class="oj-flex oj-flex-item oj-panel oj-bg-danger-30 oj-lg-padding-6x oj-md-8 oj-sm-12">
       <div id="activityItemsContainer" class="oj-flex-item oj-md-6 oj-sm-12">
       <h3 id="itemsListHeader">Activity Items</h3>
    . . .
    

    When the user selects an activity from the Activities list, the viewModel Activity Changed event handler sets the value of the activitySelected observable to true. In this case, the oj-bind-if test condition is satisfied, and the app renders the Activity Items container for the activity selection. The () notation on the observable property is the Knockout function convention to obtain the value of the observable instead of obtaining the instance of the observable object.

  3. Find the div element where id="itemDetailsContainer", and above it add the opening tag of the oj-bind-if custom HTML element, with the test attribute set to the state of the itemSelected observable.

    . . .
       </oj-list-view>
    </div>
    <oj-bind-if test="[[itemSelected()]]">
    <div id="itemDetailsContainer" class="oj-flex-item oj-panel oj-bg-neutral-30 oj-md-6 oj-sm-12">
       <h3>Item Details</h3>
    . . .     
    

    When the user selects an item from the Activity Items list, the viewModel Item Changed event handler sets the itemSelected observable to true. In this case, the oj-bind-if test condition is satisfied, and the app renders the Item Details container for the activity item selection.

  4. At the bottom of the dashboard.html file, count two closing </div> tags up, and then add the closing </oj-bind-if> tag to match the opening <oj-bind-if test ="[[activitySelected()]]"> tag. Count up one more closing </div> tag, and then add the closing </oj-bind-if> tag for the opening <oj-bind-if test ="[[itemSelected()]]"> tag.

    . . . 
                </oj-chart>
                </div>
             </div>
          </oj-bind-if>
          </div>
       </oj-bind-if>
       </div>
    </div>
    
  5. Below the closing </oj-bind-if> tag that you added, closest to the end of the file, insert an oj-bind-if test="[[!activitySelected()]]" custom HTML element that contains a div element with Oracle JET oj-flex-item oj-sm-6 flex layout helper classes.

    . . . 
                </oj-bind-if>
             </div>
          </oj-bind-if>
          <oj-bind-if test="[[!activitySelected()]]">
             <div class="oj-flex-item oj-sm-6">
             <p>Select an Activity to see Items</p>
             </div>
          </oj-bind-if>
       </div>
    </div>
    

    The Oracle JET oj-sm-6 helper class specifies that the container for the Select an Activity to see Items heading occupies six container columns for small and larger screen sizes.

    Until the user selects an activity, the value of activitySelected observable is false. Likewise, if the user presses the Ctrl key and clicks an already selected activity, the viewModel treats this event as a deselection, and the Activity Changed event handler sets the activitySelected observable to false. In both cases, the oj-bind-if test condition is satisfied by the boolean false condition, and the app renders the Select an Activity to see Items heading.

  6. Below the first closing </oj-bind-if> tag in the file, add an oj-bind-if test ="[[!itemSelected()]]" custom HTML element that contains a div element with Oracle JET oj-flex-item oj-sm-12 oj-md-6 flex layout helper classes.

    . . .
                      </oj-chart>
                   </div>
                </div>
             </oj-bind-if>
             <oj-bind-if test="[[!itemSelected()]]">
                <div class="oj-flex-item oj-sm-12 oj-md-6">
                   <p>Select an Item to see details</p>
                </div>
             </oj-bind-if>
             </div>
          </oj-bind-if>
          <oj-bind-if test="[[!activitySelected()]]">
             <div class="oj-flex-item oj-sm-6">
             <p>Select an Activity to see Items</p>
             </div>
          </oj-bind-if>
       </div>
    </div>
    

    The Oracle JET oj-sm-12 and oj-md-6 helper classes specify that the container for the Select an Item to see details heading occupies six container columns for medium and larger screen sizes or occupies twelve container columns for small screen sizes.

    Until the user selects an activity item, the value of the itemSelected observable is false. Likewise, if the user presses the Ctrl key and clicks an already selected activity item, the viewModel treats this event as a deselection, and the Item Changed event handler sets the itemSelected observable to false. In both cases, the oj-bind-if test condition is satisfied by the boolean false condition, and the app renders the Select an Item to see details heading.

  7. Save the dashboard.html file.

    Your file should look similar to event-task3-dashboard-html.txt.

Task 4: Remove Redundant Style Classes

In earlier tutorials of these learning path series, we added Oracle JET style classes to make a visual contrast between the containers that we added to the web app. Now that we have completed the implementation of the master-detail pattern, we can remove these style classes.

  1. Navigate to the JET_Web_Application/src/ts/views directory and open the dashboard.html file in an editor.

  2. Find and delete the following style classes that are used in the dashboard.html file.

    oj-bg-danger-30
    oj-bg-info-30
    oj-bg-neutral-30
    oj-bg-warning-20
    oj-panel
    
  3. Save the dashboard.html file.

    Your file should look similar to final-event-dashboard-html.txt.

Task 5: Test the Master and Detail Lists

  1. In the terminal window, change to the JET_Web_Application directory and run the web app.

    npx ojet serve
    
  2. In the web browser, click the Baseball activity in your app.

    The Activities list selection triggers the selectedActivityChanged event handler. The Activity Items container is rendered for the selected activity.

    Activity Items rendered for the Baseball activity

    Description of the illustration master_detail_list.png

  3. In the Activity Items list, click SureCatch Baseball Glove.

    The Activity Items list selection triggers the selectedItemChanged event handler. The app renders the Item Details container for the selected item.

    Activity Items rendered for the Baseball activity

    Description of the illustration master_detail_item.png

  4. In the Activity Items list, press Ctrl and click SureCatch Baseball Glove to deselect it.

    The Activity Items list deselection triggers the selectedItemChanged event handler. The Item Details container is hidden.

    Deselecting SureCatch Baseball Glove hides Item Details

    Description of the illustration master_detail_list.png

  5. Resize the browser, or press Ctrl+Shift+I to bring up the Chrome DevTools and select a smaller screen size, such as Pixel 7, from the screen-size emulator.

    The containers are arranged according to the screen size.

    The containers rearrange to fit a small screen size

    Description of the illustration resized_master_detail_list.png

  6. Close the browser window or tab that displays your running web app.

  7. In the terminal window, press Ctrl+C, and if prompted, enter y to exit the Oracle JET tooling batch job.

Task 5: (Optional) Run a Web App from a Restored App

If you want to run the completed Oracle JET web app from the supplied code, you can restore the app from the downloaded archive file. To work with a “stripped and zipped” Oracle JET web app, you must restore project dependencies, including Oracle JET tooling and the required libraries and modules, within the extracted app.

  1. Download the jet_web_application_masterdetail_final.zip file and extract the contents of the completed app to the jet_web_application_masterdetail_final folder.

  2. In the terminal window, navigate to the jet_web_application_masterdetail_final folder and restore the Oracle JET web app by installing the NPM packages that it requires.

    npm install
    
  3. Wait for a confirmation message similar to the following.

    . . .
    added 284 packages in 59s
    

    The web app is ready to run.

  4. Run the web app and test it in the browser.

    npx ojet serve
    
  5. Close the browser window or tab that displays your running web app.

  6. In the terminal window, press Ctrl+C, and if prompted, enter y to exit the Oracle JET tooling batch job.

Next Step

To proceed to the first tutorial in the next learning path in this series, click here.

More Learning Resources

Explore other labs on docs.oracle.com/learn or access more free learning content on the Oracle Learning YouTube channel. Additionally, visit education.oracle.com/learning-explorer to become an Oracle Learning Explorer.

For product documentation, visit Oracle Help Center.