In the previous chapter, we took a closer look at the components available in Sencha Touch. However, simply creating components isn't enough to build an application. The components still need to communicate with each other in order to make our application do anything truly useful. This is where events come into play.
In this chapter, we will examine events in Sencha Touch: what they are, why we need them, and how they work. We will discuss how to use listeners and handlers to make your application react to the user's touch as well as to events happening behind the scenes. We will also cover some helpful concepts such as observable capture and event delegation. We will finish up with a walkthrough of the touch-specific events and a look at how you can get more information from the Sencha Touch API.
The chapter will cover the following points:
Events
Listeners and handlers
Ext.util.Observable
Event delegation
Touch-specific events
Additional information on events
As programmers, we tend to think of code as an orderly sequence of instructions, executing one line, and then the next, and the next. It's easy to lose sight of the fact that our code really spends a lot of time sitting and waiting for the user to do something. It's waiting for the user to press a button, open a window, or select from a list. The code is waiting for an event.
Typically, an event occurs right before or right after a component performs a specific task. When the task is performed, the event is broadcast to the rest of the system, where it can trigger specific code or be used by other components to trigger new actions.
For example, a button in Sencha Touch will trigger an event whenever it is tapped. This tap can execute code inside the button that creates a new dialog box, or a panel component can "listen" to what the button is doing and change its color when it "hears" the button trigger a tap
event.
Given that most applications are intended for human interaction, it's safe to say that a lot of the functionality of your programs will come from responding to events. From a user's perspective, the events are what make the program actually "do" something. The program is responding to the user's request.
In addition to responding to requests, events also have an important role to play in making sure that things happen in the correct order.
Albert Einstein once remarked, "The only reason for time is so that everything doesn't happen at once". While this might seem like an offhand comment, it actually has a great deal of relevance when it comes to writing code.
As we write our code in Sencha Touch, we are directing the web browser to create and destroy components on the user's screen. The obvious limitation of this process is that we cannot manipulate a component before it gets created, nor after it's destroyed.
This seems pretty straightforward at first glance. You would never write a line of code that tries to talk to a component on the line before you actually create the component, so what's the problem?
The problem has to do with asynchronous actions within the code. While most of our code will execute sequentially or in a synchronous fashion, there are a number of cases where we will need to send out a request and get back a response before we can proceed. This is especially true in web-based applications.
For example, let's say we have a line of code that builds a map using a request from Google Maps. We will need to wait until we have received a response from Google and rendered our map before we can begin fiddling about with it. However, we don't want the rest of our application to freeze while we wait on the response. So, we make an asynchronous request, one that happens in the background, while the rest of our application goes about its business.
These asynchronous requests are called AJAX requests. AJAX stands for Asynchronous JavaScript and XML. If we configure one of our buttons to send out an AJAX request, the user can still do other things while the application is waiting for a response.
On the interface side of things, you will probably want to let the user know that we made the request and are currently waiting for a response. In most cases, this means displaying a "loading" message or animated graphic.
Using events in Sencha Touch, we can show the loading graphic by tying into the beforerequest
event in the AJAX component. Since we need to know when to make the loading message disappear, our component will wait for the requestcomplete
event from our AJAX request. Once that event fires, we can execute some code to tell the loading message to go away. We can also use the requestexception
event to inform the user whether errors occurred during the request.
Using this type of event-driven design allows you to respond quickly to the user's actions, without making them wait for some of the more time-consuming requests your code needs to perform. You can also use the events to inform the user of errors. The key to events is getting your other components to "listen" for the event, and then telling them how to handle the information they receive.
Every component in Sencha Touch has a long list of events that it generates. Given the number of components you will likely have in your application, a lot of chatter is going on.
Imagine a party with 100 people, all having lots of different conversations. Now imagine trying to pick out all of the useful information from each conversation. It's impossible. You have to focus on a specific conversation in order to gather anything useful.
In much the same way, components also have to be told what to listen for, or else, such as our unfortunate partygoer, they would quickly be overwhelmed. Fortunately for us, there's a confuration for that.
A listeners
configuration tells the component what events it needs to pay attention to. Listeners can be added like any other configuration option in Sencha Touch. For example, the configuration option on a panel might look like the following:
listeners: { tap: { element: 'body', fn: function(){ Ext.Msg.alert('Single Tap'), } } }
This configuration option tells the panel to listen for the tap
event, when the user taps once on the body
element of the panel. When the tap
event occurs, we execute the function listed in the fn
configuration option (this is typically referred to as a handler). In this case, we pop up a message box with the words Single Tap.
Notice that the items in our listeners
configuration are always part of an object (curly braces on either side), even if there is only one event we are listening for. If we were to add a second event, it would loolike the following:
listeners: { tap: { element: 'body', fn: function(){ Ext.Msg.alert('Single Tap'), } }, hide: { fn: function(){ this.destroy(); } } }
We can also get information back from the listener and use it in our handler functions. For example, the tap
event sends back the event
object, the DOM element that was clicked, and the listener
object itself, if we have the following listener on a panel:
listeners: { tap: { element: 'body', fn: function(event, div, listener) { console.log(event, div, listener); } } }
When the user taps inside the panel, we will get the following information on the console:
Arguments for events
You will notice that certain values are passed to our event by default. These default values can be found in the Sencha Touch API event documentation for each component, at http://docs.sencha.com/touch/1-1/.
Each event will have its own default values. Select a component from the Sencha API documentation, and then click Events at the top of the page, to see a list of all events for the component. The description of each event will include its default arguments.
As you can see from the console, our event
object contains a Unix timestamp for when the tap occurred, the x
and y
coordinates of the tap itself, as well as the entire content of the div
tag that was tapped. You may have also noticed that our tap
event is referred to as a click
event in our debug output. In Sencha Touch, the tap
and click
events are aliased to one another. This preserves compatibility between the desktop browser's traditional click
event and the mobile browser's tap
event.
We can use all of this information inside our function.
For this example, we will create a simple panel with a red container. Our tap listener will change the size of the red box to match where we tap on the screen:
new Ext.Application({ name: 'TouchStart', launch: function() { var eventPanel = new Ext.Panel({ fullscreen: true, layout: 'auto', items: [{ xtype: 'container', width: 40, height: 40, id: 'tapTarget', style: 'background-color: #800000;', }], id: 'eventPanel', listeners: { tap: { element: 'body', fn: function(event, div, listener) { var cmp = Ext.getCmp('tapTarget'), cmp.setWidth(event.xy[0]); cmp.setHeight(event.xy[1]); console.log(event.y); } } } }); this.viewport = eventPanel; } });
If we run this code with the console open, we can see that the X and Y coordinates of where we tap appear in the console. Our box also grows or shrinks to match these values.
As you can see from the code, we listen for the tap
event. We then grab the container
component using Ext.getCmp('tapTarget'),
, and change the size, based on the value we got back from the tap
event:
tap: { element: 'body', fn: function(event, div, listener) { var cmp = Ext.getCmp('tapTarget'), cmp.setWidth(event.xy[0]); cmp.setHeight(event.xy[1]); console.log(event.xy); } }
Since event.xy
is an array, we need to grab the individual values using event.xy[0]
and event.xy[1]
.
Listeners can also be added to a component dynamically. If we were to add a new listener to our previous example, it would look something as the following:
var cmp = Ext.getCmp('tapTarget'), cmp.on('resize', function() { var h = this.getHeight(); var w = this.getWidth(); this.update('height: '+h+'<br>width: '+w) }, cmp);
This code will get the height and width of the container. It will then use the update
method to add the height an
d width as text to the container, when the resize
event fires.
However, there is one slight problem with this approach: the resize
event only fires when a container is manually resized by dragging the lower-left corner of a manually-resizable container. Since ours is changed programmatically, the resize
event is never fired.
We can fix this by manually firing the event in our previous code using the fireEvent()
method:
listeners: {
tap: {
element: 'body',
fn: function(event, div, listener) {
var cmp = Ext.getCmp('tapTarget'),
cmp.setWidth(event.xy[0]);
cmp.setHeight(event.xy[1]);
console.log(event.xy);
cmp.fireEvent('resize'),
}
}
}
The fireEvent()
method can be used with both existing events as well as your own custom events.
While Sencha Touch components respond to a large number of events, it can sometimes be helpful to fire custom events within your application.
For example, you could fire a custom event called vikinginvasion
, using the same type of syntax as our previous example:
cmp.fireEvent('vikinginvasion'),
You can then add a listener in your code for vikinginvasion
, along with a function to handle the event:
var cmp = Ext.getCmp('tapTarget'), cmp.on('vikinginvasion', function() { alert("Man The gates!"); }, this);
You can also check a component to see if it has a specific listener, using the hasListener()
method:
var cmp = Ext.getCmp('tapTarget'), if(cmp.hasListener('vikinginvasion') { console.log('Component is alert for invasion'), } else { console.log('Component is asleep at its post'), }
There are also a number of helpful options you can use to contrl how the listeners will check for events.
For the most part, listeners can simply be configured with the event name, handler, and scope, but sometimes you need a bit more control. Sencha Touch provides a number of helpful options to modify how the listener works:
delay
: This will delay the handler from acting after the event is fired. It is given in milliseconds.
single
: This provides a one-shot handler that executes after the next event fires and then removes itself.
buffer
: This causes the handler to be scheduled to run as part of an Ext.util.DelayedTask
component. This means that if an event is fired, we wait a certain amount of time before we execute the handler. If the same event fires again within our delay time, we reset the timer before executing the handler (only once).
element
: This allows us to specify a specific element within the component. For example, we can specify a body within a panel for a tap
event. This would ignore taps to the docked items and only listen for a tap on the body of the panel.
target
: This will limit the listener to the events coming from the target and it will ignore the same event coming from any of its children.
Using the different listener options would look something like the following:
var cmp = Ext.getCmp('tapTarget'), cmp.on('vikinginvasion', this.handleInvasion, this, { single: true, delay: 100 });
This example would add a listener for vikinginvasion
and execute a function called this.handleInvasion
. The handler would only execute once, after a 100-millisecond delay. It would then remove itself from the component.
This basic list of configuration options gives you quite a bit of flexibility when adding listeners. However, there is one additional configuration option available in listeners that will require a bit more explanation. It's called scope
.
Within your handler function is a special variable called this
. Usually, this
refers to the component that fired the event, in which case, the scope
would typically be set to scope: this
. However, it's possible to specify a different value for scope
in your listener configuration. From our previous example, a change in scope
might look like the following:
tap: { element: 'body', scope: eventPanel, fn: function(event, div, listener) { this.setWidth(event.xy[0]); this.setHeight(event.xy[1]); console.log(event.xy); } }
In this example, the scope
(and as such, the variable this
) has been changed to our eventPanel
component. We can now set values directly, instead of having to use Ext.getCmp('tapTarget'),
to get the panel at the beginning of our function.
We can also set the scope
component in a similar fashion, by using the
on
method to add a listener:
var myPanel = new Ext.Panel({…}); var button = new Ext.Button({…}); button.on('click', function() { console.log('This should be myPanel:', this); }, myPanel);
Here, we added myPanel
as an argument after the handler definition, which means that when the click
event is fired and the handler function is called, you will be able to access myPanel
by referring to this
.
Within handler functions, you aren't guaranteed to have access to the same variables when you define the function. Changing the scope of a function can allow you to access a specific variable that's not easy to get to with Ext.getCmp()
or Ext.get()
. It can also be a simple convenience for getting to the component you are most likely to use in the function.
While scope
may be a hard concept to grasp, it is a very useful part of the listener configurations.
Normally, listeners are removed automatically when a component is destroyed. However, sometimes you will want to remove the listener before the component is destroyed. To do so, you'll need a reference to the handler function you created the listener with.
So far, we've been using anonymous functions to create our listeners, but if we're going to remove the listener, we have to do it a bit differently:
var myPanel = new Ext.Panel({…}); var myHandler = function() { console.log('myHandler called.'), }; myPanel.on('click', myHandler);
This can be a good practice, since it allows you to define the handler functions once and reuse them wherever you need them. It also allows you to remove the handler later:
myPanel.removeListener('click', myHandler);
In some cases, listeners are part of a relationship between two objects, and when one of the objects is destroyed, the listener is no longer necessary.
For example, say you have two panels, panel1
and panel2
, and you want to change the size of panel1
to match the size of panel2
, whenever panel2
is resized. You could put a listener on the resize
event for panel2
, but if panel1
were to be destroyed, the listener would still be there.
You could add an additional listener to panel1
that would wait for the
destroy
event and then remove the listener from panel2
, but that could become cumbersome quickly.
You can get around this particular problem by using a managed listener. A managed listener works a little bit differently from a regular listener:
var panel1 = new Ext.Panel({…}); var panel2 = new Ext.Panel({…}); panel1.addManagedListener(panel2, 'resize', function() { console.log('Panel 2 was resized.'), panel1.setSize(panel2.getSize()); } );
This can get a bit confusing, because when we call panel1.on()
or panel1.addListener()
, we're adding a listener to panel1
. However, when we call addManagedListener()
, the first argument is actually a different component we're adding the listener to. In this case, we're adding a resize
listener to panel2
that will automatically be removed if panel1
is destroyed.
Essentially, addManagedListener
adds listeners that clean up after themselves, which can help greatly with memory management.
As you might have noticed from some of our previous code, buttons have a default configuration called handler
. This is because the purpose of a button is generally to be clicked or tapped. The handler
configuration is just useful shorthand for adding the tap
listener. As such, the following two pieces of code do exactly the same thing:
var button = new Ext.button({ text: 'press me', handler: function() { this.setText('Pressed'), } }) var button = new Ext.button({ text: 'press me', listener: { tap: { fn: function() { this.setText('Pressed'), } } } });
This same default handler behavior applies to tabs as well. The handler simply serves as a quick way to access the most routinely used event for the component.
Sometimes, you will want to keep components from firing events. Perhaps you want to do some additional processing on data returned from an AJAX query, or you want to write some custom code to handle resizing your component. Observable
gives you a way to do so via the
suspendEvents()
and resumeEvents()
methods. You can call these methods on any object that extends Observable
, such as a panel:
var myPanel = new Ext.Panel({…}); myPanel.suspendEvents(); myPanel.setHeight(100); myPanel.setWidth(100); myPanel.resumeEvents();
Normally, the setHeight()
and setWidth()
functions cause the resize
event to fire. In this example, though, we essentially put the panel to sleep while we resize it, and then wake it back up when we're done. In this case, the resize
event will never fire, so any components listening for that event will never hear it.
Note that we only suspended events on the myPanel
object. If we had resized another panel at the same time, then that panel's events would still have fired.
This is very useful when you need to do things behind the scenes in your application, but sometimes you'll want to have the events fire after you're done with your work, so that the other components can catch up. In that case, simply pass true
as the argument to suspendEvents()
:
myPanel.on('resize', function() { console.log('Resized!'), }); myPanel.suspendEvents(true); myPanel.setHeight(100); myPanel.setHeight(100); console.log('Resuming Events.'), myPanel.resumeEvents();
You can see how the Resuming Events line comes before the resize
events. This is because we didn't fire any events until after the console.log()
and resumeEvents()
calls.
You should be very careful with suspending events. Much of the built-in Sencha Touch functionality relies heavily on events, and suspending them can cause unexpected behavior.
Let's take a look at our old friend Ext.Component
and see some of the common events available to us. Remember, since most of our components will inherit from Ext.Component
, these events will be common across most of the components we use.
Most of our events will fall into two categories. The first set of events revolves around the creation of the component.
When the web browser executes your Sencha Touch code, it writes the components into the web page as a series of div
, span
, and other standard HTML tags. These elements are also linked to code within Sencha Touch that standardizes the look and functionality of the component for all supported web browsers. This process is referred to as rendering the component.
This rendering takes place in a number of stages, each of which fires an event:
These events give you a number of places to interact with your component before, during, and after the rendering process.
The second set of events is concerned with the actions taken by or done to the component itself. These events include:
These events provide a way to base the actions of your code on what is being done by, or done to, your components.
Each component will also have some specific events associated with it. For a list of these events, please consult the API docs at http://docs.sencha.com/touch/1-1/. Just select a component from the list on the left side and click the Events button at the top of the page.
Ext.util.Observable
is the base class that handles listening to, and firing of, events for all Sencha Touch components. Any class that fires events extends Ext.util.Observable
. For the most part, you won't need to directly use Ext.util.Observable
itself, since it comes built into almost every Sencha Touch component, but there are a few cases where using it directly can make things easier.
Sometimes, over the course of building an application, you will find yourself adding the same listeners to the same kind of objects multiple times. For large applications, this can take up quite a bit of memory. That's where Ext.util.Observable.observe()
comes in. This method will allow you to add listeners to a class, instead of a particular instance of that class. Normally, when we add listeners, we do something such as this:
var panel = new Ext.Panel({…}); panel.on('resize', function(){…});
The listener will only run if that exact panel is resized. To add a listener to all Ext.Panel
components, you can pass the component constructor you want to observe, then add your listeners:
Ext.util.Observable.observe(Ext.Panel); Ext.Panel.on('resize', function(){…});
You'll notice that we didn't create a new panel here. Instead, we added the resize
listener to Ext.Panel
itself. Now, any panel you create with the new Ext.Panel()
component, it will have the resize
listener enabled automatically.
Additionally, this gives you a single place to update when you need to make changes to the resize
listener and function. This type of class-based listener can save time, memory, and a lot of headaches.
Ext.util.Observable.capture
is a static method. This means you won't have to create a new instance of an object—you can call it directly. This method will call a handler for every single event that an object fires, which can come in handy when you're trying to figure out if you've added listeners to the proper event and if those events are firing.
Using our resizing eventPanel
example, add the following line after this.viewport = eventPanel;
:
Ext.util.Observable.capture(eventPanel, function() { console.log('The eventPanel fired an Event:', arguments); });
As you can see, this generates a lot of console lines. The first argument to the function will always be the event name, and the subsequent arguments will be the arguments that are typically passed to that particular event. If you want to start capturing events only after a certain known event fires, you can add the capture
statement to a listener. For example, if you wanted to only start capturing events after the panel was rendered, you would do someting such as the following:
listeners: { render: { fn: function(myPanel) { Ext.util.Observable.capture(eventPanel, function() { console.log('The eventPanel fired an Event:', arguments); }); } } }
Now, Ext.util.Observable.capture
will only be started once the render
event has fired. From that point on, it will continue to capture all events fired by the panel.
You can stop capturing with Ext.util.Observable.releaseCapture()
, as well. Say we want to stop capturing events after a resize
event. Then, in our capture
function, we could do the following:
Ext.util.Observable.capture(eventPanel, function() { console.log('The eventPanel fired an Event:', arguments); if (arguments[0] == 'resize') { Ext.util.Observable.releaseCapture(eventPanel); } });
When a component in Sencha Touch fires an event, the event "bubbles up" the chain to the parent component. This gives us some interesting opportunities with regards to memory and efficiency.
One of the common uses of event delegation is in lists. Let's say, we have a list of people such as those you would find in a common address book. When a name in the address book is clicked, we switch to the details panel with all of the contact information. This is a pretty straightforward setup most of us would recognize.
However, let's say we want to add a phone icon to each of our list items. When the phone icon gets clicked, the person's phone number is dialed. You might be inclined to add a click
handler for each icon, but this is a very bad idea, because all of those listeners take up space in memory.
An address book with 400 people would have 400 listeners. This will slow down a web-based application, as it tries to listen to 400 separate elements within the DOM (in addition to everything else in your code that has a listener).
However, you can get around this problem using event delegation.
Let's start with a very simplified version for our contact list:
var contactList = new Ext.List({ tpl: '<tpl for="."><li><img src="images/phone.png"/><h1>{contactName}</h1></li></tpl>', listeners: { el: { tap: callContact, delegate: 'img' } } });
Unlike our previous examples, where we instructed the component which event to listen to, in this case, we tell the component where to listen for the event. In this case, we chose el
, which is a property common to all components and basically means within the item (we could also use body).
Now that the component knows where to listen, we tell it what to listen for on the line tap: callContact
. This also tells the List
component what to run when the tap
event occurs.
The last line, delegate: 'img'
delegates the event to any img
tag inside our List
component. In this case, it would be our phone.png
icon on each row of the list.
The result is a single listener, which checks to see if an image is tapped in our list.
This saves on memory, and also means that if you add or remove any list items, you don't have to add and remove listeners, too.
In addition to component events, Sencha Touch also understands a number of touch-specific events. These events include:
touchstart
: An event that records the initial contact point with the device.
touchend
: An event that records where the contact ended on the device.
touchmove
: An event that records where the touch moved (this one will fire off as a series of events that map the path of the user's touch along the screen).
touchdown
: An event that records when the element is touched as part of a drag or swipe.
dragstart
: An event that records when the element is initially dragged.
drag
: Similar to touchmove
, drag
tracks the path of the element, when dragged.
dragend
: An event that records where the element stopped being dragged.
singletap
: A single tap on the screen. This will fire once for the first tap, when a screen is double-tapped. It will not fire on the second tap.
tap
: A tap on the screen. This will fire both for the first and second tap, when a screen is double tapped.
swipe
: A single finger brushed across the screen from left to right.
There is one small caveat to note with these touch-specific events: with the exception of tap
and doubletap
, Sencha Touch is actually receiving the events from the web browser rather than the component itself. Since the web browser is doing our listening for us, we need to attach our listener to something the web browser understands.
This means that instead of binding the event to the component itself, we have to bind the event to the underlying element of the component.
Component versus element
One of the harder concepts for people new to web programming is the relationship between the web browser and the Sencha Touch components. At its core, when a Sencha Touch component is rendered in the web browser, it gets translated into a complex series of div
and span
that the web browser can read and display. When we refer to the underlying element of the component, we are talking about one of these div
containers.
Since a WebKit-based browser, such as Safari or Chrome, is designed to understand all of our touch-specific events, the Sencha Touch component can be instructed to monitor a div
tag on the web page to see if these events occur.
Additionally, since the component can only monitor a div
tag, it can only do it after the div
tag has been rendered to the web page. This means we have to set our component to listen for the render
event, and then tell it to add the monitoring. It looks something like the following:
new Ext.Application({ name: 'TouchStart', launch: function() { var eventPanel = new Ext.Panel({ fullscreen: true, layout: 'fit', html: 'Tap Me', id: 'eventPanel', listeners: { afterRender: function() { this.mon(this.el, { swipe: this.event2Console }); } }, event2Console: function(e) { console.log(e); } }); this.viewport = eventPanel; } });
We create our panel, as usual, and we add a listener for afterRender
. This tells the panel that, once it exists within the browser window, it should execute the following code (in this case, we want it to run):
this.mon(this.el, { swipe: this.event2Console });
This will cause Sencha Touch to listen for browser events generated by panel's DOM element, rather than the panel itself. The element then listens for the swipe
event to occur. When the swipe
event occurs, we execute our function this.event2Console
.
Notice that we did things a bit differently this time. Usually, we create the function as part of the listener:
this.mon(this.el, { swipe: function(e) { console.log(e); } }
Instead of that, we added the function onto the component itself, just as a configuration object:
event2Console: function(e) { console.log(e); }
We then referenced the function as this.event2Console
. This can be incredibly useful when you want to call the function from multiple places within the component. Both methods will produce the same result: a console log with our event object.
As you can see, we get a considerable amount of information from this event, including direction, distance, deltaX, time, and an event type. We can use this information as follows:
this.update(e.type+':'+e.direction+':'+e.distance);
This method can be added to our this.event2Console
function, to update our panel with the type, direction, and distance of our swipe. Give it a try.
Also, play around with changing the
swipe
event in the code to any of the other functions in the list. Get a feel for what triggers each event and what information they return.
By using these touch events and the other events built into Sencha Touch, your program should be able to respond to just about any situation.
The best place to get information about events is the Sencha Touch API docs at http://docs.sencha.com/touch/1-1/. Select a component in the list on the left, and look for the Events button at the top. You can click Events to go to the beginning of the section or hover your mouse pointer to see the full list of events and select a specific event from that list.
Clicking the down arrow next to the event will display a list of parameters for the event and any available examples on how the event can be used.
Another good place to find out about touch-specific events is the Kitchen Sink example application (http://dev.sencha.com/deploy/touch/examples/kitchensink/). Inside the application is a Touch Events section. This section allows you to tap or click on the screen to see which events are generated from the different taps and gestures.
The WebKit team at Sencha Touch has also created an event recorder for Android. You can get more information at http://www.sencha.com/blog/event-recorder-for-android-web-applications/.
In this chapter, we have covered a basic overview of events, and how to use listeners and handlers to get your program to respond to these events. We also covered a few of the more common events and took an in-depth look at Ext.util.Observable
, which handles the events for every component in the Sencha Touch framework.
We talked about event delegation and the potential memory issues that can occur with listeners. We finished up the chapter with a look at touch-specific events and some tips on finding additional information about events.
In the next chapter, we will cover how to get and store data in Sencha Touch, using JSON, data stores, models, and forms.