Tuesday, August 31, 2010

The Javascript Grid Editor

Current Version 1.0.10
Last Updated 2010.07.15
Download GitHub
  CodePlex

 

Introduction:

The Javascript Grid Editor is a 100% Javascript program that extends the functionality of CRM Grids by providing a way to edit records and add records, in place, by providing input elements in the cells of a CRM Grid and relevant functions to write any input provided to the CRM application via SOAP interaction.


Caveats:

Firstly, please review the list of Known Bugs.

A major caveat to this code is that whomever uses it must have Read privileges to Entities, Attributes, and Relationships.  This must be provisioned through a security role they have.  Without it, the Javascript Grid Editor is unable to ascertain important elements of the entity’s configuration.  At this time, exclusion of these security attributes will cause the script to fail when executed.

Also, the Javascript Grid Editor works only to create or edit records in with a statecode value of “1” (Active, Open, etc.).  This ensures that no matter what entity for which the Javascript Grid Editor is enabled, records will only ever be created in this state, and this state is valid for editing any/all fields for those records.  There is no restriction on the value of the statuscode attribute.

Additionally, the Javascript Grid Editor is currently incapable of importing, parsing, or otherwise executing customized javascript that has been placed into the form for the entity it targets.  It’s unable to execute platform specific code for the same reason.  Therefore, interactive fields, or codependent fields that have been scripted together will not behave similarly if presented in the Javascript Grid Editor.

Finally, the Insert Mode will only work when all Business Required and System Required attributes are present on the Grid.  The Edit Mode will work with any set of attributes present in the Grid.

Installation:

Please read the Caveats on this page for deployment considerations.

  1. Download the script using the link at the top of this page.
  2. Set the variable GEO_ENTITYNAME to the schema-name of the record type to which the script will be tied.
  3. Export your existing ISV.config.xml settings from CRM.
  4. Open the exported ISV.config.xml file in an editor.  Personally, I use Visual Studio.
    WARNING: Do not use XML Notepad for this, as the script exceeds its allowed length for attribute content, and will result in a broken script.  Many other XML editors may behave similarly.
  5. Create XML elements for buttons to execute the script.
    1. Identify or create an Entity element for the entity targeted by the script in step 2
    2. Establish or use an existing Grid element within the Entity element.
    3. Establish or use an existing MenuBar element within the Grid element.
    4. Establish or use an existing Buttons element within the MenuBar element
    5. Establish two (2) new Button elements within the Buttons element.  Set their JavaScript attribute to nothing (“”) for now.
    6. Establish Titles elements within each Button element.
    7. Establish Title elements within each Titles element.  Set the LCID attribute to “1033” for English.  Set the Text attribute of one to “Edit Mode”.  Set the other to “Insert Mode”.
      WARNING: The titles of the buttons are essential to the operation of the script.  For additional language support, see Advanced Configuration for details.

      (Example: Completion of Steps 5.1 – 5.7)
      <?xml version="1.0" encoding="utf-8"?> <ImportExportXml version="4.0.0.0" languagecode="1033" generatedBy="OnPremise">   <Entities></Entities>   <Roles></Roles>   <Workflows></Workflows>   <IsvConfig>     <configuration version="3.0.0000.0">       <Root />       <!-- Microsoft Customer Relationship Management Entities (Objects) -->       <Entities>         <Entity name="serviceappointment">           <Grid>             <MenuBar>               <Buttons>                 <ToolBarSpacer />                 <Button Icon="" JavaScript="">                   <Titles>                     <Title LCID="1033" Text="Edit Mode" />                   </Titles>                   <ToolTips>                     <ToolTip LCID="1033" Text="Action Button: Edit/Save" />                   </ToolTips>                 </Button>                 <Button Icon="" JavaScript="">                   <Titles>                     <Title LCID="1033" Text="Insert Mode" />                   </Titles>                   <ToolTips>                     <ToolTip LCID="1033" Text="Action Button: Insert/Cancel" />                   </ToolTips>                 </Button>               </Buttons>             </MenuBar>           </Grid>         </Entity>       </Entities>
    8. Encode the modified script for XML safety.  Personally, I use the XML Escape Tool.
    9. Copy and paste the encoded script into the JavaScript attribute of each Button element.
  6. Save the modified XML.
  7. Upload and import the modified ISV.config.xml file.

Usage:

Using the Javascript Grid Editor is fairly straightforward.  When a Grid presenting the configured entity is displayed, two buttons, “Insert Mode” and “Edit Mode” will appear.  Clicking either one of these buttons the first time initializes the script, and the JGE will enter the appropriate mode: allowing you either to create new records, or edit existing ones directly in the grid, without the necessity of opening the record’s form.

When in either Insert or Edit modes, the names and functions of the buttons change to “Save [New/All]” and “Cancel [New/All]”.  Use these buttons to save all changes/inserts or to cancel them.

In Insert Mode, a green-arrow icon will present itself in the left-most column for the new line.  Click it to create additional insert rows for creating multiple records at once.  Additional insert rows contain a red-X icon that can be clicked to delete its row.

In Edit Mode, changing your selected records dynamically changes the records that are being edited.  Be warned, however, that if you make a change on a record, and then deselect it, your changes will be silently discarded.

Advanced Configuration:

The idea behind the script is to allow both ease of deployment for simple requirements, and complicated configuration for complex requirements.  There are many ways to configure the Grid Editor to fit your needs.  The advanced configuration options are these:

Option Function Global Variable Code Location, or [CL]
Provide support for alternate languages GEO_REGISTEREDBUTTONLABELS Internal
Restrict an attribute from receiving input controls GEO_RESTRICTEDATTRIBUTES Internal
Disable an attribute’s input controls GEO_DISABLEATTRIBUTES Internal, Internal Conditional, External
Specify default values for an attribute on insert mode. GEO_ADD_DEFAULTVALUES Internal, Internal Conditional, External

The “Code Location” specifies where you should put the customizations, and can be defined in one of three scenarios:

  • Internal:  This is the default location for all globally defined values, and overrides all other “Code Locations”.  In the code these are identified with the tag “[CL]:  Internal”.
  • Internal Conditional:  This is where values for attributes are set that predicate on the absence of External definition of the same.  In other words, if you want the attribute set with a default only when it hasn’t been set by External code, do it here.  In the code these are identified with the tag “[CL]:  Internal Conditional”.  By nature, this does not override any other location.
  • External:  This is a description of any code that externally from the Grid Editor’s code.  (See Advanced Scenarios for examples.)

Provide support for alternate languages (Internal):

By default, the Grid Editor is configured for button labels using the American English code “1033”.  In Step 5.7 of the Installation instructions, you may wish to use a different language.  If you wish to use multiple languages, this is also allowed by repeating Step 5.7 once for each language with the language-specific LCID value (Microsoft Locale ID Reference).

Once the ISV.config file has a Title element for each language specified for both buttons, it’s time to update the script with those titles.  (Note: I’m not certain how document encoding will affect this process, but I haven’t worked with anyone yet who indicated it was a problem.)

  1. The GEO_REGISTEREDBUTTONLABELS object contains two members: EditSaveButton and NewCancelButton.  Both are arrays.  Configure an index for each LCID, in each array, to be an array itself.  Example:
    GEO_REGISTEREDBUTTONLABELS.EditSaveButton[1033] = new Array(); GEO_REGISTEREDBUTTONLABELS.NewCancelButton[1033] = new Array();
  2. Use the three constants, GEO_CONSTANT_MODE_VIEW, GEO_CONSTANT_MODE_EDIT, and GEO_CONSTANT_MODE_ADD to define indexes of the sub-array with values of the labels each button will have for each mode the Grid Editor may enter.  By design, the “Edit Mode” button becomes the “Save” button, and the “Insert Mode” button becomes the “Cancel” button.  (You’re not configuring the functionality of the button, just what label it uses.)  Example:
    GEO_REGISTEREDBUTTONLABELS.EditSaveButton[1033][GEO_CONSTANT_MODE_VIEW] = "Edit Mode"; GEO_REGISTEREDBUTTONLABELS.EditSaveButton[1033][GEO_CONSTANT_MODE_EDIT] = "Save All"; GEO_REGISTEREDBUTTONLABELS.EditSaveButton[1033][GEO_CONSTANT_MODE_ADD] = "Save New";  GEO_REGISTEREDBUTTONLABELS.NewCancelButton[1033][GEO_CONSTANT_MODE_VIEW] = "Insert Mode"; GEO_REGISTEREDBUTTONLABELS.NewCancelButton[1033][GEO_CONSTANT_MODE_EDIT] = "Cancel All"; GEO_REGISTEREDBUTTONLABELS.NewCancelButton[1033][GEO_CONSTANT_MODE_ADD] = "Cancel New";

Restrict an attribute from receiving input controls (Internal):

There are times when it is inconvenient or undesirable for a field to be edited or created with user input.  The GEO_RESTRICTEDATTRIBUTES variable defines an array of strings which are attribute schema-names.  Any attribute which has its schema-name added to the array will not receive input controls in either Insert or Edit mode.  Example:

var GEO_RESTRICTEDATTRIBUTES = new Array("statuscode", "expireson");

Disable an attribute’s input controls (Internal, Internal Conditional, External):

Disabling input controls offers one advantage to restricting them outright: the ability to assign the value of an attribute by script, while preventing the user from entering data.  The GEO_DISABLEATTRIBUTES variable defines an array of strings which are attribute schema-names.  Specifying a disabled attribute is identical to specifying a restricted one.  Example:

var GEO_DISABLEATTRIBUTES = new Array("statuscode", "expireson");

(Note: Disabling an attribute which is already “restricted” does nothing.  Restriction takes precedence.)

Specify default values for an attribute on Insert mode (Internal, Internal Conditional, External):

The GEO_ADD_DEFAULTVALUES object has two members: Properties and Collections.  A Collection is a special type of property for Lookup attributes that contain multiple values, such as the “Resources” field on the Service Activity entity.  All other attributes belong to Properties.  (This is similar to the returned object from the RetrieveRecord() function.)

Entity attributes are defined as members of either Properties or Collections with appropriate values.

Example to define a simple text value for the title attribute:

GEO_ADD_DEFAULTVALUES.Properties['title'] = "This is a default title.";

Example to define a simple picklist value for the statuscode attribute:

GEO_ADD_DEFAULTVALUES.Properties['statuscode'] = '1';

Lookups are a little more difficult, but uncomplicated nonetheless.  They require the same information/values as you would provide a Lookup on an entity’s form with Javascript.  Two additional attributes must be defined, aside from the target-record’s GUID: the name, and the entity type.  These can be identified by appending the name of the attribute with the text “name” and “type”, respectively.

Example to define a “Case” value for the regardingobjectid lookup attribute:

GEO_ADD_DEFAULTVALUES.Properties['regardingobjectid'] = recordId; GEO_ADD_DEFAULTVALUES.Properties['regardingobjectidname'] = recordName; GEO_ADD_DEFAULTVALUES.Properties['regardingobjectidtype'] = 'incident';

…where recordId is a string containing the GUID of the target record, and recordName is the target’s value of the default attribute for the target entity type (commonly the “title”, or “name” attribute).

Finally, Collections which represent arrays of records within a single lookup attribute follow the same requirement as for regular Lookup fields.  Each record identified in the value must have 3 total attributes: id, name, and type.  However, these are assembled into an array, which is a member of the Collections object.

Example to define one value for the “Customers” of a Service activity:

GEO_ADD_DEFAULTVALUES.Collections['customers'] = new Array(); GEO_ADD_DEFAULTVALUES.Collections['customers'][0] = new Object(); GEO_ADD_DEFAULTVALUES.Collections['customers'][0]['partyid'] = recordId; GEO_ADD_DEFAULTVALUES.Collections['customers'][0]['partyidname'] = recordName; GEO_ADD_DEFAULTVALUES.Collections['customers'][0]['partyidtype'] = 'account';

…again, where recordId and recordName identify a particular ‘account’ record.

Advanced Scenarios:

Dynamically Specifiy Configuration Parameters for Related Views:

Because the script is designed to be set once for all views for a particular entity, each view naturally performs identically.  This isn’t exactly desirable for embedded or associated views.  At this point, the Javascript Grid Editor understands practically nothing about the context of the view in which it operates.

Traditionally the “New” button on an associated view establishes new records with and automatic relationship to the parent record.  The Javascript Grid Editor, however, is WYSIWYG—if a field isn’t present in the view, or is not provided a value, it doesn’t get set.  It was for handling this situation that I designed the GEO_DISABLEATTRIBUTES and GEO_ADD_DEFAULTVALUES variables to be configured externally from the script.

Accomplishing the task of dynamically configuring these variables is somewhat tricky, for two reasons:

  1. Attempts to make their configuration dynamic in the script body itself (as deployed to ISV.config) will be generally undesirable because, as I stated before, the script is meant to simply provide static configuration information.  The script runs identically for every view.  It is better to push the dynamic configuration from the parent form, which lends to:
  2. The iFrame containing the view, for associated or embedded views, will not be loaded until first accessed.  Meaning that the configuration push must wait until the view is available.

The path I have personally taken, is to generate a configuration script in a Javascript string, and force-feed that string into execution within the iFrame, using the IncludeScript() function from my CRM Javascript Library.  Consider the example below, where I have changed the configuration of the script for use with an Embedded Advanced Find View, which places a view of Service Activities within the Case form:

SAScript = "GEO_ADD_DEFAULTVALUES = new Object();"   + "GEO_ADD_DEFAULTVALUES.Properties = new Object();"   + "GEO_ADD_DEFAULTVALUES.Collections = new Object();"   + "GEO_ADD_DEFAULTVALUES.Properties['statuscode'] = '1';"   + "GEO_ADD_DEFAULTVALUES.Properties['regardingobjectid'] = '" + crmForm.ObjectId + "';"   + "GEO_ADD_DEFAULTVALUES.Properties['regardingobjectidname'] = \"" + crmForm.all.title.DataValue + "\";"   + "GEO_ADD_DEFAULTVALUES.Properties['regardingobjectidtype'] = 'incident';"   + "GEO_ADD_DEFAULTVALUES.Collections['customers'] = new Array();"   + "GEO_ADD_DEFAULTVALUES.Collections['customers'][0] = new Object();"   + "GEO_ADD_DEFAULTVALUES.Collections['customers'][0]['partyid'] = '" + crmForm.all.customerid.DataValue[0].id + "';"   + "GEO_ADD_DEFAULTVALUES.Collections['customers'][0]['partyidname'] = \"" + crmForm.all.customerid.DataValue[0].name + "\";"   + "GEO_ADD_DEFAULTVALUES.Collections['customers'][0]['partyidtype'] = \"" + crmForm.all.customerid.DataValue[0].typename + "\";"   + "GEO_DISABLEATTRIBUTES = new Array('regardingobjectid','customers');";  var SAFrame = crmForm.all.IFRAME_ServiceActivities;  SAOnReadyStateChange = function() {   if (SAFrame.readyState == "complete") {     var SADoc = SAFrame.contentWindow.document;     IncludeScript(SADoc, SAScript);   } }  SAFrame.attachEvent("onreadystatechange", SAOnReadyStateChange);

In this example, I am setting the values of statuscode, regardingobjectid, and customers and disabling both regardingobjectid and customers, in Javascript contained by the string SAScript.  What I’m doing, is dynamically assembling Javascript for the iFrame to execute, when it’s ready.

I set the regardingobjectid, because this establishes the relationship to the parent Case for new records created by the Insert Mode; then, I disable it to prevent users from changing the value.  As a convenience, I’ve also dynamically set the customers and statuscode values, so that other information is inherited from the Case.  It is therefore, absolutely necessary for statuscode, regardingobjectid, and customers to appear on within the view.

Next, I isolate the iFrame’s element into SAFrame, which I can use to attach an event handler for the its “onreadystatechange” event.  This handler calls the IncludeScript() function (not shown), which forces the variable SAScript into a new <script> element within the iFrame.

The result is that when the view enters “Edit Mode”, both customers and regardingobjectid are disabled for editing.  However, when the view enters “Insert Mode”, these fields are provided the defaults I established by the script above.  Simple enough, right?  :P

The Javascript Grid Editor by David Berry is licensed under the Apache License 2.0.


standard entropy values

No comments:

Post a Comment