Using Option Set Options with the REST Endpoint - JScript (Microsoft Dynamics CRM 2011 and Microsoft Dynamics CRM Online)

Applies To: Microsoft Dynamics CRM 2011, Microsoft Dynamics CRM Online

Jim Daly
Microsoft Corporation

May, 2011


In Microsoft Dynamics CRM 2011 and Microsoft Dynamics CRM Online you can use attribute metadata retrieved using the SOAP endpoint in a Web resource application that uses the REST endpoint for data operations. This article describes a sample JScript library that uses attribute metadata to generate a simple user interface that allows you to perform data operations on entity records. This sample JScript library focuses on metadata for option set options.

The download package includes a Visual Studio 2010 solution and a Microsoft Dynamics CRM 2011 managed solution to illustrate the concepts presented in this article.

Applies To

  • Microsoft Dynamics CRM 2011

  • REST endpoint for Web resources

  • SOAP endpoint for Web resources

  • JScript


Microsoft Dynamics CRM 2011 introduced the REST endpoint for Web Resources. This new endpoint provides access to perform data operations using a RESTful style API. The REST endpoint for Web resources is an Open Data Protocol (OData) endpoint called the Organization Data Service. For more information about the REST endpoint for Web resources see the SDK topic Use the REST Endpoint for Web Resources.

The REST endpoint is the preferred endpoint for data operations from Web resources in Microsoft Dynamics CRM. It provides an easy to use alternative to the SOAP endpoint for Web resources. The REST Endpoint is ideal for many programming tasks in Web resources that require create, retrieve, update and delete operations. However, the REST endpoint does not provide all the capabilities available in the SOAP endpoint. For example, it does not execute messages. Microsoft Dynamics CRM metadata is only available by executing messages.

This metadata includes important information if you want to create a custom user interface using Web resources. Because Microsoft Dynamics CRM entities and attributes can be customized it is important that your user interface is designed to adapt to those changes using the current metadata. Attributes that include options are one of the items of entity metadata that can change frequently. Your Web resources that provide a user interface should be designed to use entity and attribute metadata so that they can adapt to changes. Metadata also includes other useful information such as localized label text and default values. Using metadata in your design allows your solution to adapt to configuration changes in the Microsoft Dynamics CRM organization.

Using metadata also promotes reusability of code. As the sample application demonstrates, you can create more generic solutions that can be applied to different entities using a design that uses configuration data rather than being limited to a specific entity.

This article will discuss how you can combine data retrieved from both the REST endpoint and the SOAP endpoint to create a metadata-driven user interface that includes data for the different types of option set attributes.

Table of Contents

Option Set Attributes

Other Attribute Metadata Properties

Sample JScript Web Resource Application

Retrieve Attribute Metadata

Build the Table

Retrieve the Initial Set of Records

Render the Form

Editing Records

Saving Records

Creating Records

Deleting Records

Option Set Attributes

There are four types of attributes that include options as shown in the following table.

UI Name SDK Name Attribute Type Comments

Option Set



This is the attribute that most people think of as an option set (or picklist) attribute.

PicklistAttributeMetadata derives from the abstract EnumAttributeMetadata class. The OptionSet property is an OptionSetMetadata object that defines a collection of OptionMetadata objects. Each OptionMetadata object represents an option to be displayed for the attribute.

Two Options



Boolean attributes store a Boolean data value but they also allow for labels to explain what each value means. The labels can be pairs like “Yes/No”, “True/False”, “Allow/Do not allow”. These labels can be customized. This pairing of the Boolean value with a label is implemented using a design that shares many of the same concepts as option set attributes.

The BooleanAttributeMetadata.OptionSet property returns a BooleanOptionSetMetadata object. Unlike Picklist attributes, this OptionSet does not have an Options property. It has a TrueOption and a FalseOption. Each of these contain an OptionMetadata object. Unlike PicklistAttributeMetadata, BooleanAttributeMetadata attributes do not inherit from EnumAttributeMetadata.

Status Reason



Status attributes are similar to the Option Set attributes because they both inherit from the EnumAttributeMetadata class.

However, there is one important difference: Valid Status attribute options depend on current State of an entity record. The StatusAttributeMetadata.OptionSet is an OptionSetMetadata object that defines a collection of StatusOptionMetadata objects.StatusOptionMetadata includes a State property that stores the value of the State option value that is the valid parent for the Status option.




State Attributes control the state of the record. These states are often Active and Inactive but can vary by entity. For more information about the default values, see Default values of Status and Status Reason attributes.

The StateAttributeMetadata.OptionSet is an OptionSetMetadata object that defines a collection of StateOptionMetadata objects. StateOptionMetadata provides the DefaultStatus and InvariantName properties. The DefaultStatus property describes which Status option is the default for each State option.

You can’t set the value of a State attribute using the REST endpoint. You need to use the SetState message with the SOAP endpoint.

Setting the state of a record is usually done by using a control outside of a form, for example, by using a button on the Ribbon. It is not typical that a form will include a Silverlight ComboBox or HTML select control that allows setting the state of a record.


A fifth type of attribute that uses options is the EntityName (EntityNameAttributeMetadata) attribute. The Entity Name attribute is usually a child attribute of a lookup attribute. For example, the EntityName attribute type is also used by the ActivityPointer.ActivityTypeCode attribute to specify the type of activities available in the organization. This type of attribute is not exposed in the user interface by the application or used to store customer data. It is not discussed in the remainder of this article.

Other Attribute Metadata Properties

In addition to the options defined for each of the option set attributes the following table describes other properties that are important when creating a custom user interface.

Property Description


Specifies the default value of an EnumMetadataAttribute.


Specifies the default value of a BooleanMetadataAttribute.


Provides the localized label for the attribute.


This is true when the attribute can be included when a new record is created.


This is true when the attribute can be displayed in the application. Certain attributes of an entity allow the application to manage the record and shouldn’t be displayed in the application.


This is true when the attribute can be updated. Certain attributes, such as the CreatedOn date attribute cannot be updated.


The property contains information about whether data in this attribute must be included to save a record.

Sample JScript Web Resource Application

A Visual Studio 2010 solution named OptionSetSampleJScript is available in the download package This solution contains the definitions of a set of Web resources that provide a framework for creating a simple user interface that allows for performing create, retrieve, update, and delete operations on entity records using the REST endpoint while you use the SOAP endpoint to retrieve attribute metadata. A managed solution ( is included with the download package. This solution includes Web resources so that you can install it and see the code running on your Microsoft Dynamics CRM organization. Because it is a managed solution you can also easily uninstall (delete) the solution.


The OptionSetSampleJScript solution is intended to illustrate the principles discussed in this article. It does not represent a fully implemented framework for generating a user interface.

Supporting Libraries

The OptionSetSampleJScript solution uses the JScript libraries in the following table.

Library Description


Contains functions to retrieve metadata using the SOAP endpoint for Web resources. The sample uses the RetrieveAttributeAsync function to execute the RetrieveAttribute message.

This library also includes functions to execute the following messages but they are not used in this sample:


Contains functions to support the following operations using the REST endpoint for Web Resources:

  • RetrieveMultiple

  • Create

  • Update

  • Delete


This is the Microsoft Dynamics CRM 2011 page that provides the functions to gain access to context information from the application.


A minified version of the json2.js file so that the sample can work with Internet Explorer 7. This library is a public domain library created by Douglas Crockford. It is available for download here.


This library contains the properties and methods that provide the features of the sample. This library is the topic of the next section.


The SDK.OptionSetSample.js file defines configuration properties and methods to support creation of a simple user interface that provides the following capabilities:

  • Retrieve a list of records filtered by the text found in the primary attribute

  • Display the list of records in an HTML table

    • Primary attributes and lookup attributes provide links to open the record in Microsoft Dynamics CRM
  • Create new records

  • Update selected attributes for the record

  • Delete Records

The user interface for the example ContactPage.htm is shown in the following screenshot:

The sample ContactsPage.htm

This user interface uses a positioned HTML div element that allows for editing the selected row of the table.

The SDK.OptionSetSample.js library is used by an HTML Web resource that sets properties to the SDK.OptionSetSample object defined in the library. The HTML page represents a template that could be used for any entity when the correct configuration information is passed into it.

The SDK.OptionSetSample requires the following configuration data in the following table to be set in the HTML Web resource.

Property Description


The schema name for the entity to use.


The schema name for the primary attribute for the entity.


The schema name for the String attribute used when filtering records. This is typically the PrimaryAttribute, but it could be any String attribute.


The schema name for the String attribute that is the primary attribute for the entity.


A String Array that contains the schema names of all the entity attributes that are used in the page.


An object that includes the following properties:


The id of the HTML table element where the table that displays records will be displayed.


The id of the HTML tbody element in the table that will include rows displaying the list of records.


A Boolean value indicating whether to make the first column of the table a check box to allow for selection of records for deletion.


The id of the HTML input element that will be used to filter records displayed in the table.


The id of the HTML button element that will be used to display the form to create a new record.


The id of the HTML button element that will be used to delete selected records.


An Array of the Schema names of attributes to be displayed in the table. Each of these attributes must be included in the SDK.OptionSetSample.ColumnSet list.


An object that includes the following properties:


The id of the HTML div element that will be used when creating or editing records.


The id of the HTML button element that will be used to save records.


The id of the HTML element that will be used to close the form when it is clicked.

Retrieve Attribute Metadata

The first action that an HTML Web resource using the SDK.OptionSetSample.js library performs when the page loads is to retrieve the attribute metadata for each of the attributes configured in the SDK.OptionSetSample.Columnset. This action uses the SDK.OptionSetSample.getAttributeMetadataCollection.Execute function. This function sends asynchronous requests for each attribute using the SDK.MetaData.RetrieveAttributeAsync function.


It is also possible to call the SDK.Metadata.RetrieveEntityAsync method specifying that the entity definition include the attributes. This would return all the attributes in a single operation. However, that probably is not desirable most of the time. In the case of the Account entity, there are 190 attributes and any user interface may only require a fraction of them. Performance is improved by only requesting required AttributeMetadata definitions.

Because later steps expect that the attribute metadata has been retrieved, the sample does not allow for additional processing until all the attribute metadata is retrieved. The Success callback is SDK.OptionSetSample.getAttributeMetadataCollection.Success. Each time that function is called it adds the metadata to an array that contains AttributeMetadata objects and then compares the length of the SDK.OptionSetSample.ColumnSet with the length of the array of metadata objects. When they are equal, all the expected attribute metadata has been retrieved.

After all the attribute metadata has been retrieved the SDK.OptionSetSample.OnMetadataRetrieved function is called to continue actions that must be performed when the application loads for the first time. These tasks are as follows:

  1. Build the Table

  2. Retrieve the Initial Set of Records

  3. Render the Form

Build the Table

The SDK.OptionSetSample.buildTable function creates the thead element of the table by looping through the columns defined in the SDK.OptionSetSample.TableData object. For each attribute column, the attribute DisplayName property UserLocalizedLabel is used to set the heading text for each column.

Finally, a tbody element for the table is appended but no rows are added to it. The tbody element id is set using the configuration data from SDK.OptionSetSample.TableData.BodyId.

Retrieve the Initial Set of Records

The SDK.OptionSetSample.retrieveMultipleRecords.Execute method calls the SDK.REST.retrieveMultipleAsync function to request the initial set of records. No filter criteria are specified for this initial request. The number of records returned is the REST endpoint default limit of 50 records. The Success callback calls the SDK.OptionSetSample.processRetrievedRecordProperties function on each record. This processing is described in Transforming Retrieved Record Properties. This process creates JScript objects that contain all the string text that is required to display the data in a form or a grid.

After the records are retrieved they are added to the SDK.OptionSetSample.RecordCollection array and the SDK.OptionSetSample.refreshRecordList function generates the HTML to populate a row in the table for each record. The id attribute for each row contains the GUID value for each record. The SDK.OptionSetSample.OnRowClick function is attached to the onclick event for each row. This function displays the form so that the record can be edited.

For option set attributes, a new Label property is used to display the data in a table cell. For Customer, Owner, or Lookup attributes the data returned from the REST endpoint is sufficient to create a link to open the record in a new window using the information provided in the SDK topic: Open Forms, Views, and Dialogs with a URL.

Transforming Retrieved Record Properties

The data retrieved from the REST endpoint is an array of JSON objects. Many properties are simple data types where the values are just string values. The following code example represents the JSON value for a Contact record that has been formatted for readability.

 "__metadata": {
  "uri": "https://serverName/OrganizationName/XrmServices/2011/OrganizationData.svc/ContactSet(guid'a66c0739-1f7b-e011-80c6-00155db059be')",
  "type": "Microsoft.Crm.Sdk.Data.Services.Contact"
 "Address1_City": "Redmond",
 "FamilyStatusCode": {
  "__metadata": {
   "type": "Microsoft.Crm.Sdk.Data.Services.OptionSetValue"
  "Value": 1
 "Department": "Purchasing",
 "StateCode": {
  "__metadata": {
   "type": "Microsoft.Crm.Sdk.Data.Services.OptionSetValue"
  "Value": 0
 "OwnerId": {
  "__metadata": {
   "type": "Microsoft.Crm.Sdk.Data.Services.EntityReference"
  "Id": "f466b19e-057b-e011-80c6-00155db059be",
  "LogicalName": "systemuser",
  "Name": "First name Last name"
 "ParentCustomerId": {
  "__metadata": {
   "type": "Microsoft.Crm.Sdk.Data.Services.EntityReference"
  "Id": "426c0739-1f7b-e011-80c6-00155db059be",
  "LogicalName": "account",
  "Name": "A Store (sample)"
 "JobTitle": "Purchasing Assistant",
 "EMailAddress1": "",
 "Address1_StateOrProvince": "WA",
 "FullName": "Adrian Dumitrascu (sample)",
 "GenderCode": {
  "__metadata": {
   "type": "Microsoft.Crm.Sdk.Data.Services.OptionSetValue"
  "Value": 1
 "AccountRoleCode": {
  "__metadata": {
   "type": "Microsoft.Crm.Sdk.Data.Services.OptionSetValue"
  "Value": 2
 "ContactId": "a66c0739-1f7b-e011-80c6-00155db059be",
 "LastName": "Dumitrascu (sample)",
 "FirstName": "Adrian",
 "CreatedOn": "\/Date(1305043496000)\/"

Process for Transforming Data

The process depends on matching the record property name with the AttributeMetadata.SchemaName. When the SchemaName is known, the AttributeMetadata.AttributeType is known. When the names match, the record property can be processed and transformed based on the AttributeType. The following diagram describes the process.

Process to transform record Properties

Goals for Transforming Data

The goals for transforming data are:

  • Add the Corresponding Text for Option Set Property Values to Be Displayed In a Grid

  • Track Which Properties Have Changed

  • Convert the String Date Values into Jscript Date Objects

Add the Corresponding Text for Option Set Property Values to Be Displayed In a Grid

Each Option Set and Boolean value is converted into an object that has a Value and Label property. The Label value is the Label.UserLocalizedLabel.Label taken from the AttributeMetadata so that they always are displayed in user’s selected language.

For Boolean attributes the Label is mapped to the OptionSet TrueOption or FalseOption label depending on the value of the property.

Picklist attributes can be handled in the same manner. If the value is null, the Label is set to an empty string.

Status and State attributes are processed separately just because each option possesses a StatusOptionMetadata or StateOptionMetadata property instead of the OptionMetadata property used by Picklist and Boolean attributes.

Track Which Properties Have Changed

When each property is retrieved, a Boolean IsDirty property is added and set to false. When individual properties are later edited in the application this property will be set to true so that any updates will only be applied for the data that has actually changed.

Convert the String Date Values into Jscript Date Objects

This is accomplished by the success callback for SDK.REST.retrieveMultipleAsync. The SDK.REST._retrieveMultipleResponse function includes the dateReviver function as a parameter to the JSON.Parse function as shown in the following example:

dateReviver: function (key, value) {
  var a;
  if (typeof value === 'string') {
   a = /Date\(([-+]?\d+)\)/.exec(value);
   if (a) {
    return new Date(parseInt(value.replace("/Date(", "").replace(")/", ""), 10));
  return value;


This function looks for property values that matches the regular expression '/Date\(([-+]?\d+)\)/' and converts the value into a JScript Date.


The date values returned by the REST endpoint are the UTC values. When regular JScript Date functions such as Date.toString are used to display the dates in the UI they show the date and time that corresponds to the user’s operating system settings. However, Microsoft Dynamics CRM does not use the user’s operating system settings. Microsoft Dynamics CRM uses the user’s preferences. Therefore the values displayed using regular Date object methods may be different than those displayed in Microsoft Dynamics CRM when the user’s operating system settings are different from their Microsoft Dynamics CRM user preferences.

If you want to reconcile the displayed date and time values to match the values shown in Microsoft Dynamics CRM you can query data stored in the UserSettings entity and entities like TimeZoneDefinition and TimeZoneRule to create functions to display dates that match the user’s preference. Microsoft Dynamics CRM does not provide functions to perform these actions.

Render the Form

The form is not displayed until a row in the table is selected or the Create button is clicked. The form is just an HTML div element that contains a button to save the current record and a span element hides the form when it is clicked.

The form fields are rendered by looping through each attribute in the SDK.OptionSetSample.ColumnSet. For each attribute that should be displayed in the form there must be a placeholder HTML div element that has an id attribute value that matches the pattern <Attribute Name>+”Field”. A form field is not created if this placeholder is not found.

When a placeholder is found the attribute metadata is referenced to confirm that the attribute is valid for update, create, and read actions. This sample has not implemented separate form definitions for each action so each selected attribute must allow for all actions.

Then an HTML control is created and appended to the placeholder.

Creating Controls for Fields

This sample implements a very simple form structure. In the SDK.OptionSetSample.renderFormField function, for each HTML div placeholder on the page a simple HTML control is generated in script and appended to the placeholder element. Each control includes an HTML span element to provide the label. The attribute DisplayName.UserLocalizedLabel.Label provides the content for the label. Each control id attribute value is set to the schema name of the attribute.

For attributes that have options, an HTML select control is populated with options. An HTML input control is provided for String attributes.

For Boolean attributes only two options are provided. The option value set is to either true or false with the corresponding TrueOption or FalseOptionLabel.UserLocalizedLabel.Label.

For Picklist attributes an option is provided for each of the OptionSet.Options. The option value is the OptionSetMetadata.Value and the text value is the OptionMetadata.Label.UserLocalizedLabel.Label. An option is also added to represent a null value.

Setting options for any Status attributes is deferred until the context of the record is set because there is a dependency on the StateCode attribute value. Some Status options are only valid for certain states.

At this time the SDK.OptionSetSample.OnFieldChanged function is attached to the onchange event for each field.

Finally, based on the value of the AttributeMetadata.RequiredLevel.Value, a red asterisk is appended to the field if it is required by the system or the application to provide a visual indicator that the field requires a value for the record to be saved.

Editing Records

Because the SDK.OptionSetSample.OnRowClick function was attached to the onclick event for each row of the table, the form to modify the record appears when you click a row. This function uses the SDK.OptionSetSample.setCurrentRecord function to set the SDK.OptionSetSampleCurrentRecord variable and set the values of this record in the form for editing.

However, before it does this the SDK.OptionSetSample.CurrentRecord is checked to see whether there are any unsaved changes that must be applied before the Current record is changed. The user is prompted to save changes or cancel before the context is changed to the new record.

Tracking Changes to the Current Record

The SDK.OptionSetSample.ChangedAttributes array is initialized by the SDK.OptionSetSample.SetCurrentRecord function every time a new record is made the current record.

Each form field has the SDK.OptionSetSample.OnFieldChanged function attached to the onchange event. This function sets the isDirty property for the current record to true in addition to the isDirty property for the changed property. This function also uses the SDK.OptionSetSample.evaluateSaveButtonEnable function to enable the save button only after some property in the current record has been changed.

The Save button uses the SDK.OptionSetSample.saveCurrentRecord function. Before saving the record, this function inspects each property of the current record and, if the isDirty value is true, it pushes the name of the attribute into the SDK.OptionSetSample.ChangedAttributes array.

Saving Records

To preserve the accuracy of any data in the entity that is being audited it is important that only values that are changed are saved. Because any changes to values of the current record are tracked the SDK.OptionSetSample.saveCurrentRecord function can isolate only the changed attributes. This is performed by checking the isDirty property that was added when the records were retrieved. The names of any changed attributes are then added to the SDK.OptionSetSample.ChangedAttributes array.

During a save operation an empty JScript object is instantiated as the recordToSave variable. Then the code loops through the values in the SDK.OptionSetSample.ChangedAttributes array and appends properties for each attribute to the object. The Values of the current record are then set on the new properties of the recordToSave object. This is very easy to do in JScript because objects are loosely typed.

The metadata for each changed attribute is then retrieved and it is processed based on the attribute type. By using this data it is also possible to check if any of the required field properties have been set to null. Use the AttributeMetadata.RequiredLevel.Value to check whether any of the changed attributes are required. The following code snippet sets the Boolean value of Required for each attribute:

var Required = (AttributeMetadata.RequiredLevel.Value == "SystemRequired" || AttributeMetadata.RequiredLevel.Value == "ApplicationRequired") ? true : false;

The following code snippet from SDK.OptionSetSample.saveCurrentRecord shows how a Picklist attribute is processed to prepare to save the record.

  1. Capture the value from the form

  2. Check whether the value is null yet required and cancel the save if this is true

  3. Update the object that represents the record to be saved

  4. Update the currentRecord to set the new values

switch (AttributeMetadata.AttributeType) {
case "Picklist":
     var sourceField = document.getElementById(AttributeName);
     var value = parseInt(sourceField.value);
     if (value == null && Required) {
      var message = "The " + AttributeMetadata.DisplayName.UserLocalizedLabel.Label + " field is required.";
     var label = sourceField.options[sourceField.selectedIndex].text;
     recordToSave[AttributeName] = {};
     recordToSave[AttributeName].Value = value;
     SDK.OptionSetSample.CurrentRecord[AttributeName].Value = value;
     SDK.OptionSetSample.CurrentRecord[AttributeName].Label = label;

Creating Records

When the Create button is clicked, the SDK.OptionSetSample.OnCreateClick function uses the SDK.OptionSetSample.instantiateDefaultRecord function to create a new empty object and set it as the current record.

The SDK.OptionSetSample.instantiateDefaultRecord function depends on the attribute metadata to assemble a JScript object that matches a new record that can be saved. One important difference from a record that is retrieved is that the record does not have a value for the entity primary attribute, the record Id. The Id will be generated by the system.

For the remaining attributes defined in the SDK.OptionSetSample.ColumnSet array, each is processed using the attribute metadata. If the attribute is required and valid for create operations, the IsDirty property is set to true. This ensures that the property is validated when it is saved. One important exception to this rule is the OwnerId attribute. Although this is required and valid for create, it is set automatically with a reference to the current user when the record is saved. Because this sample does not support the assign operation, it is assumed that the owner data will not be edited in the sample.

When instantiating a new record you can also use the metadata to set any default values.

Setting Default Values

There is information in the attribute metadata about default values for option set attributes.

For Boolean attributes the BooleanAttributeMetadata.DefaultValue property contains the default value.

For Picklist and State attributes the DefaultFormValue contains either the default value or -1 to indicate that there is no default value.

The default value for Status attributes is more complex and requires some calculation. Remember that each Status attribute option is mapped to a specific State attribute option using the StatusOptionMetadata.State property. Conversely, each StateOptionMetadata has a DefaultStatus property to tell which Status option is the default for that State. The process used to get the default value for a particular Status option is:

  1. Get the default State attribute value.

  2. Loop through the State options to locate the StateOptionMetadata representing that value.

  3. Check the DefaultStatus property of that option. This value represents the default Status option value.

Deleting Records

The sample allows for deleting records in a manner similar to the Microsoft Dynamics CRM 2011 application grid. There is a check box for each row in the table. Selecting one or more rows enables the delete button. Each time that you check a row it adds the Id to the SDK.OptionSetSample.SelectedRecords array.

When you click the delete button you are prompted to confirm you want to delete the selected records. Clicking OK will pass the record id values to the SDK.OptionSetSample.deleteRecord.Execute method. That message adds the SDK.OptionSetSample.RemoveDeletedRecord function as the success callback to the SDK.REST.deleteAsync method. After the record is successfully deleted, the callback performs the following actions:

  • Removes the record from the SDK.OptionSetSample.RecordCollection array.

  • Removes the row from the table representing that record.

  • Removes the reference to the record in the SDK.OptionSetSample.SelectedRecords array.


The code in the OptionSetSampleJScript solution illustrates some the factors you must consider when you are creating a custom user interface using the REST endpoint for Web resources. Retrieving attribute metadata using the SOAP endpoint for Web resources provides the information that you must have to display text for option set values in addition to information about default values, required values, and the eligibility of attributes to participate in different types of operations. Finally, the metadata also provides the capability to create more generic solutions with later opportunities to reuse code.

Send comments about this article to Microsoft.