Skip to main content

Event notifications

Many MWF component and modules have custom events that must be communicated to other web page code. Most DOM events are traditionally handled by setting up event listeners that respond to the event and invoke event handler code to do what is needed to respond to the event. However, this mechanism is not needed for most action oriented modules and components because MWF uses a publish-subscribe messaging pattern to communicate their custom events. The messages even include data to supply details about the event. This document explains the implementation of this pattern in MWF.

Pub/Sub

The publish-subscribe pattern (also known as “pub/sub”) has been around for several years. It can take several forms, but we are concerned here with how it works in MWF and that will be the focus of our discussion.

In many MWF controls, there is a publisher and there is a subscriber. The publisher is usually a module or a component and, when a certain kind of event occurs, the publisher sends out a notification about that event. That notification is a message that contains some data about the details of the event. Any application code that is interested in knowing about that event can subscribe with the publisher to receive all notifications. The publisher maintains a list of all of the subscribers and, when a notification is sent out, it is sent to each subscriber on the list.

interaction diagram of the publish-subscribe process

This messaging scheme is lightweight and synchronous in nature. It is essentially a 1:1 messaging mechanism and is just 1-way: publisher to subscriber. That the publisher can have multiple subscribers just means that the 1:1 messaging is repeated for each subscriber.

All modules and components that issue notifications use the same mechanism throughout MWF. While it coexists with DOM events, except for those that are specifically blocked by the module or component, those events are more difficult to use. This is because there is a lot going on inside a module or complex component. There are multiple DOM events occurring that drive the internal logic of the module or component. Its custom notifications generally report the end result of all of that internal logic. And if you try to access one of those internal events directly, it will have virtually no meaning to your code because it will lack the context of that internal logic.

Message types

While all notifications contain data, they do not all use the same data types. Therefore, it is important to know the format of the data sent with the notification.

The message data is not structured into complex headers, payloads, and checksums. As a lightweight system functioning in a confined environment, such data complexity is not necessary. The data is simply an object with properties contextually associated with the component or module and its events.

Each module or component using event notification should have the specific details of its message data identified in its documentation. A summary is provided in the list below.

List of notifications   

Control Notification event Details
alert onAlertClosed No data
auto-suggest onMatchPatternChanged Data array:
  type [string]
  value [string] or IAutoSuggestProduct
  attributes1 [Attribute]

Note: The IAutoSuggestProduct object can be any of the following:
  title [string]
  targetUrl [string]
  imageSrc [string]
  backgroundColor [string]
  category1 [string]
  isImageRound1 [boolean]
pagination onPageChanged Data array:
  page [number]
  priorPage [number]
  internal [boolean]
  userInitiated [boolean]
combo onSelectionChanged Data array:
  id [string]
  value [string]
  internal [boolean]
  userInitiated [boolean]
dialog   onShown No data
onHidden No data
onButtonClicked Data array:
  Button [HTMLElement]
select onSelectionChanged Data array:
  id1 [string]
  href1 [string]
  internal [boolean]
  userInitiated [boolean]
slider onValueChanged Data array:
  value [number]
  internal [boolean]
  userInitiated [boolean]
hero item onHeroItemClicked Data array:
  preventDefault [boolean]
  event [UIEvent]
  targetElement [HTMLElement]
  targetUri [string]
Immersive hero item onHeroItemClicked Data array:
  preventDefault [boolean]
  event [UIEvent]
  targetElement [HTMLElement]
  targetUri [string]
multi-feature onSlideRangeChanged Data array:
  fullyVisibleItemRange [number]
  partiallyVisibleItemRange [number]
  userInitiated? [boolean]
multi hero item  onBreakpointChanged Data array:
  breakpoint [number]
  width2 [number]
onHeroItemClicked Data array:
  preventDefault [boolean]
  event [UIEvent]
  targetElement [HTMLElement]
  targetUri [string]
action menu onSelection Data array:
  id1 [string]
action toggle onActionToggled Data array:
  toggled [boolean]
button onClick Emulates standard DOM click event
carousel onSlideRangeChanged Data array:
  fullyVisibleItemRange [number]
  partiallyVisibleItemRange [number]
  userInitiated? [boolean]
checkbox onValueChanged Data array:
  checked [boolean]
pivot  onControllerIndexChanged Data array:
  previousIndex [number]
  currentIndex [number]
onPivotChanged Data array:
  activePivotId [string]
  previousPivotId [string]
sequence indicator  onIndexChanged Data array: index [number]
onControllerIndexChanged Data array:
  previousIndex [number]
  currentIndex [number]
toggle onToggled Data array:
  checked [boolean]
dateTime picker onDateTimeChanged Data array:
  newDateTime [string]
  oldDateTime [string]
Note: Each string is formatted as a JavaScript Date Object.
1. Cannot be null.
2. Standard breakpoint widths in pixels = 0, 540, 768, 1084, 1400, 1779

Note: This hidden table was created using a content toggle component

How it works

The pub/sub mechanism is coded into each module and component that uses it. Let us examine the auto-suggest module as an example.

  • In this case, the data included in the notification is a string. The string contains the user's entry into the input field.
  • The event is identified as: “onMatchPatternChanged”
  • The module (as publisher) will send the notification to the subscriber's function of “onMatchPatternChanged()”
  • The module sends the notification to its list of subscribers, starting with the first and repeating for each subscriber in the list.

Sequence of events

Now for a closer look at some sample code.

  1. The component is initialized.
  2. During initialization, the application code subscribes to the module's event notifications. The example code below illustrates what is happening.
  3. JavaScript
    
    14      function initAutoSuggest() {
    15          mwf.ComponentFactory.create([{
    16              component: mwf.AutoSuggest,
    17              selector: '#my-auto-suggest',
    18              callback: function (autoSuggests) {
    19                  if (autoSuggests && (autoSuggests.length > 0)) {
    20                      window.autoSuggest = autoSuggests[0];
    21                      if (!!autoSuggest) {
    22                          autoSuggest.subscribe({
    23                              onMatchPatternChanged: function (notification) {
    24                                  (function (pattern) {
    25                                      return $.ajax({
    26                                          type: "get",
    27                                          url: "https://www.your.suggestion.service.com/&pattern=" + pattern,
    28                                          datatype: "json"
    29                                      });
    30                                  })(notification.pattern)
    31                                  .done(function (data) {
    32                                      window.autoSuggest.updateSuggestions(parseYourSuggestions(data));
    33                                  })
    34                                  .fail(function (jqXHR, textStatus, err) {
    35                                      window.autoSuggest.updateSuggestions([]);
    36                                  });
    37                              }
    38                          });
    39                      }
    40                  }
    41              }
    42          }]);
    43      }
    
                    
  4. An event occurs in the module: the user enters some text.
  5. The module sends out an event notification.
  6. The application code receives the notification and processes the notification data.
  7. The pub/sub process is complete.

Discussion

Let us take a look at this example line by line.

The initialization function begins on line 14. Like is common throughout most MWF applications, the initialization callback function is used to set up the subscription for notifications. In this example we are initializing the auto suggest module. Remember that the process is the same for modules or components as long as they issue event notifications.

The component initialization starts with the ComponentFactory.create() on line 15.

Line 16 specifies what component type to initialize.

Line 17 specifies an optional selector to narrow down the initialization from all components of the type to a smaller subset, in this case using a # id selector to target a single auto suggest module.

The callback is specified in line 18. It executes once the matching components have been initialized. This create call is all there is to phase one of the initialization. The callback may or may not be called synchronously. And the second phase of initialization starts when the callback is triggered.

Line 19 is the beginning of the callback method which is passed an array of all the components of the specified type that matched the specified selector. Because this example selects a specific element by its id, it only expects to get back an array with one auto suggest component it in. After receiving the array, the first thing the callback does is verify that it got the array and that it has at least one element in it. Otherwise there would be an error.

Line 20 stashes the auto suggest component reference globally in the window. Alternatively, it could be held in a global variable or some other place. The key is to have it available somewhere handy. Doing this means that, when you need it again, you do not have to reacquire it using ComponentFactory.create() again.

You subscribe to the auto suggest event notifications at line 22. Auto suggest subscribers must implement an onMatchPatternChanged callback function to receive and process the notification. In this example it is implemented using an anonymous object with the method body inline. Alternatively, if a class implemented this method, then the pointer to an instance of the class could be passed to subscribe() instead.

Lines 23 through 37 shows an example of the auto suggest module's subscribers' onMatchPatternChanged callback being implemented inline and passed a notification object that has a pattern property. In this example, the subscriber makes an ajax call to a service that will return matching suggestions for the pattern. After the ajax call is made, the subscriber's onMatchPatternChanged callback is exited.

Line 31 shows the ajax done callback that will be executed at some future point after the matching suggestions are returned from the service.

And line 32 shows the service data being parsed/transformed into an array of suggestions in the format expected by the auto suggest’s updateSuggestions() method.

The important thing to remember from this example is how the unique requirements of the auto suggest module blend into the subscride/callback functionality in such a complementary way. Every module and component that used pub/sub goes through the same process.

To view a stand-alone working example of the code we have been discussing, just click on the Show button, below. Copy the code and run it as an HTML file. You can use your browser's developer tools to examine the code and see what is happening.

HTML

<!doctype html>
<html class="no-js" lang="en" dir="ltr">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="x-ua-compatible" content="ie=edge">
        <title>Auto suggest module</title>
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <meta name="description" content="For development testing of Microsoft.com Web Framework">
        <meta name="keywords" content="Development Site">
        <meta name="author" content="Microsoft Corporation">
        <link id="primary-stylesheet" rel="stylesheet" href="/css/mwf-west-european-default.css" data-dir="ltr" data-market="mwf-west-european" data-theme="default" data-partner="mwf" data-default-ltr="/css/mwf-west-european-default.css" data-default-rtl="/css/mwf-arabic-default.css" data-alt-ltr="/css/mwf-west-european-alt.css" data-alt-rtl="/css/mwf-arabic-alt.css">
        <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.1.1.min.js"></script>
        <script type="text/javascript">
            function initAutoSuggest() {
                mwf.ComponentFactory.create([{
                    component: mwf.AutoSuggest,
                    selector: '#my-auto-suggest',
                    callback: function (autoSuggests) {
                        if (autoSuggests && (autoSuggests.length > 0)) {
                            window.autoSuggest = autoSuggests[0];
                            if (!!autoSuggest) {
                                autoSuggest.subscribe({
                                    onMatchPatternChanged: function (notification) {
                                        (function (pattern) {
                                            return $.ajax({
                                                type: "get",
                                                url: "https://www.your.suggestion.service.com/&pattern=" + pattern,
                                                datatype: "json"
                                            });
                                        })(notification.pattern)
                                        .done(function (data) {
                                            window.autoSuggest.updateSuggestions(parseYourSuggestions(data));
                                        })
                                        .fail(function (jqXHR, textStatus, err) {
                                            window.autoSuggest.updateSuggestions([]);
                                        });
                                    }
                                });
                            }
                        }
                    }
                }]);
            }
        </script>
    </head>
    <body onload="initAutoSuggest();">
        <div data-grid="container">
            <div class="m-context-mwf-item-info">
                <h1 class="c-heading-1">Auto suggest</h1>
                <form class="c-search" autocomplete="off" name="form1" target="_self">
                    <input aria-label="Enter your search" role="combobox" aria-controls="my-auto-suggest" aria-autocomplete="both" aria-expanded="false" type="search" name="search-field" placeholder="Search">
                    <button class="c-glyph" name="search-button"><span class="x-screen-reader">Search</span></button>
                    <div class="m-auto-suggest" id="my-auto-suggest" role="group">
                        <ul class="c-menu" aria-hidden="true" data-js-auto-suggest-position="default" tabindex="0" role="listbox"></ul>
                        <ul class="c-menu f-auto-suggest-no-results" aria-hidden="true" data-js-auto-suggest-position="default" tabindex="0">
                            <li class="c-menu-item"><span tabindex="-1">No results</span></li>
                        </ul>
                    </div>
                </form>
            </div>
        </div>
        <script id="primary-javascript" src="/scripts/mwf/bundle/mwf-main.var.js"></script>
    </body>
</html>
    

Summary

While not all MWF module or components use pub/sub, many of them do and they use it the same way. This consistency in the pub/sub process means that the same steps for subscribing and receiving the notifications are repeatable wherever needed in your code. The major advantage of these notifications is that it enables a more efficient methodology for dealing with specialized modules and components that involve significant levels of internal logic and complexity. The pub/sub methods allows you a simple interface to those specialized controls. Your application code is less complex and lets you focus on larger design issues.