Resco JavaScript Bridge: Difference between revisions

Jump to navigation Jump to search
(Created page with "'''Resco JavaScript Bridge''' (also known as '''JSBridge''') is a part of the Resco Mobile CRM application (client application) which offers interaction of custom HTML pag...")
 
Line 82: Line 82:


== External and third-party HTML/JavaScript libraries ==
== External and third-party HTML/JavaScript libraries ==
Compatibility limitations also apply to third-party HTML/JavaScript libraries that you might want to use. Resco mobile apps don't limit the use of such libraries in any way, however. make sure that they work on the targeted mobile browser (iOS – Safari, Android – Chrome, Windows – Edge/Internet Explorer). Also, we recommend performing tests on the actual devices due to Web Browser Component context and the limitations/disabled features and tags.
Depending on the code of the external library, it might or might not run. So far, we have successfully used many third-party libraries and components, including popular frameworks like jQuery or D3 for data visualization.
== Asynchronous approach ==
Most of the JSBridge methods are asynchronous. This approach comes from the platform limitations and is required by the internal implementation.
The asynchronous approach practically means – very simply put – that when you call a method or a function, it won’t return the value or result immediately, but the code continues to run and only afterwards, sometime in the future a callback method containing the result will be called.
Typically, when writing synchronous code, you call a method and it returns the result ''immediately'' – your code waits until the result is returned. For example, if you have a method for returning an Account – getAccount() – and you will call it in following synchronous way, you can use the returned Account immediately.
<syntaxhighlight lang='js'>
var account = getAccount();
account.DoSomething();
</syntaxhighlight>
However, if getAccount is an asynchronous method, the code does not wait for the result (asynchronous methods typically don’t return the result) and therefore you couldn’t call DoSomething directly afterwards, because account wouldn’t contain data yet. For that, a callback method is usually employed. Here is the code rewritten in an asynchronous fashion.
<syntaxhighlight lang='js'>
getAccount(processAccount);
// 1 – a lot of code can be here
function processAccount(account) {
   account.DoSomething();
}
</syntaxhighlight>
ProcessAccount function is the callback which gets the result as a parameter, once the Account is retrieved. Only in the callback method, it is possible to process this parameter – if we try to do it directly after the getAccount, this would result in an error. The comment // 1 can be replaced by an arbitrary amount of code and the callback function can be called anytime in between or afterwards.
If you are not used to asynchronous approach, you might run into some of the errors described in the next section.
=== Common errors ===
One of the most common errors is using the asynchronous result before the result callback is called. The following snippet shows such a situation (see how this can be tricky to spot because of the inline callback function):
;Bad:
<syntaxhighlight lang='js'>
var entity = null;
   MobileCRM.DynamicEntity.loadById(id, function (err) {
      if (err) alert(err);
      else entity = this;
   });
   // “entity” is still “null” here
   MobileCRM.bridge.alert(entity.primaryName);</syntaxhighlight>
;Good:
<syntaxhighlight lang='js'>
MobileCRM.DynamicEntity.loadById(id, function (err) {
   if (err) alert(err);
   else gotEntity(this);
});
   // ... code ...
function gotEntity(entity) {
   // “entity” is already defined here
   MobileCRM.bridge.alert(entity.primaryName);
}
</syntaxhighlight>
It is important that it has two parameters – an ID of the entity to load and a callback function. In both examples, this callback is provided inline – the function is defined directly in the method’s parameter (loadById(id, function(err) { … }); ) and you can see that this can encourage using the result directly afterwards the loadById call. But that would result in an error, because the callback might not yet be called, and the entity variable might not yet contain data. The callback can be called any time after the loadById call so you can use the result only in the callback. Therefore, your code has to be structured as in the correct example above.
Another common error is calling the asynchronous method “too late”, e.g., from the form’s “onSave” handler. If the save is successful, the form is closed immediately and the asynchronous operation is aborted. The correct implementation should hold the save using SuspendSave and close the form from JavaScript after all the job is done.
;Bad
<syntaxhighlight lang='js'>
MobileCRM.UI.EntityForm.onSave(function(entityForm) {
   // asynchronous method call
   var fetch = new MobileCRM.FetchXml.Fetch(…);
   fetch.execute(…);
}, true);
</syntaxhighlight>
;Good
Please check the following reference: https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_EntityForm_suspendSave
== Data access API ==
One of the primary purposes of Resco JavaScript Bridge is exposing the data of the mobile application in JavaScript – to enable access to the records, entities, and fields available in the offline storage or directly on the CRM server. You can then use the obtained data and display them on custom HTML pages or UI components and visualize them in the required way.
The main data access API classes are:
* MobileCRM.FetchXml for querying and fetching the records
* MobileCRM.DynamicEntity for loading, saving and accessing the entity instances
* MobileCRM.Metadata for the data schema information
This chapter explores these classes in more depth while providing useful examples with explanation.
=== Fetch ===
The most common way to get data is to use [[FetchXML]], the query language you might be familiar with from Dynamics CRM. It allows creating queries to get the data, similar to SQL selects. For example, you can create a query to get all the Accounts and their associated Contacts, filtered and ordered by some values. By using objects and methods in FetchXML namespace, you can build such query in the code but you can also directly execute a query stored as XML (by using the executeFromXML method), which might come handy in some situations.
Following example shows how to create a Fetch expression in code. When executed, it will return all Accounts from Redmond, ordered by the ‘Name’ field. Building such query usually starts with creating a new instance of FetchXml.Entity and specifying which entity is queried – in this case, Accounts. Then add attributes to specify that only Account’s Name and City are returned (a convenient method for adding all attributes is also available). We add a link to associated Contacts (through Parent Customer field) and specify that we need only Full Name and Id from this (contact) entity. To define the condition, a new FetchXml.Filter is created and it is specified where the condition on the City is. Similarly, ordering of the result is defined by a new instance of FetchXml.Order.
Finally, the Fetch query is executed, and the results are processed in the success callback – one of the elements on the HTML page is filled with HTML code and the application data. In case of an error, a message box with the error is displayed.
Code:
<syntaxhighlight lang='js'>
var entity = new MobileCRM.FetchXml.Entity("account");
entity.addAttribute("name");
entity.addAttribute("address1_city");
var linkEntity = entity.addLink("contact", "parentcustomerid", "accountid", "outer");
linkEntity.addAttribute("fullname");
linkEntity.addAttribute("contactid");
entity.filter = new MobileCRM.FetchXml.Filter();
entity.filter.where("address1_city", "like", "Redmond");
var orderCity = new MobileCRM.FetchXml.Order("name");
var dataSpan = document.createElement("span");
var fetch = new MobileCRM.FetchXml.Fetch(entity);
fetch.execute("Array", function (result) {
   for (var i in result) {
      var contact = result[i]
      data.innerHTML += "<p>Account Name : " + contact[0] + "</p>" + "<p>City : " + contact[1] + "</p>"+"<p>Contact Name : " + contact[2] + "</p>"
   }
},
function (error) {
   alert("Error has occurred " + err);
},
null);</syntaxhighlight>
The JavaScript code creates a query, which corresponds to the following expression in XML:
<syntaxhighlight lang='js'>
<fetch version="1.0" aggregate="false">
   <entity name="account"><attribute name="name" />
      <attribute name="address1_city" />
   <filter>
      <condition attribute="address1_city" operator="like" value="Redmond" />
   </filter>
      <link-entity name="contact" from="parentcustomerid" to="accountid" link-type="outer">
         <attribute name="fullname" />
         <attribute name="contactid" />
      </link-entity>
   </entity>
</fetch></syntaxhighlight>
Same query can be executed directly as XML, using MobileCRM.FetchXML method as shows the following example:
<syntaxhighlight lang='js'>
var xmlData = '<fetch resultformat="Array"><entity name="account"><attribute name="accountid"/><attribute name="name"/></entity></fetch>';
MobileCRM.FetchXml.Fetch.executeFromXML(
   xmlData,
   function (result) {
      for(var i in result){
         var props = result[i];
         processAccount(props[0], props[1]);
      }
   },
   function (err) {
      alert('Error fetching accounts: ' + err);
   },
   null
);</syntaxhighlight>
Executed either way, the result of the query execution on demo data will look something like this (part of the result):
: Account Name : Amazing sports store
: City : Redmond
: Contact Name : Ben Miller
: Account Name : Amazing sports store
: City : Redmond
: Contact Name : Jan Stoklasa
=== DynamicEntity ===
Another way to work with data is to use Dynamic Entity in JSBridge. This class provides access to one entity record, allowing you to get the fields or properties, but also allows loading, saving, and deleting an instance. When you execute a FetchXML query, the result can be an array of dynamic entity objects, giving you more options for processing the results than a plain array – just specify ‘DynamicEntities’ as the first parameter in the FetchXML execute method.
In the following examples, we illustrate how to work with this class. At first, let’s see how to access an existing entity.
For that, we need to create a Dynamic Entity object, providing the ID of an existing record. In the following example, we are assuming that accountId variable holds this ID, but it is fairly simple to get it by using Fetch XML, see the code in the previous chapter.
The first step is creating a Dynamic Entity object that passes the parameters to the constructor (if you want to see all the arguments, consult JSBridge reference).
<syntaxhighlight lang='js'>
var dynamicEntity = new MobileCRM.DynamicEntity("account", accountid);
</syntaxhighlight>
If you want to access the properties or save them in a variable you can do it like this:
<syntaxhighlight lang='js'>
var props = dynamicEntity.properties;
</syntaxhighlight>
If you want to access only some of the entity properties, like ‘firstname’ (use logical name = all lowercase) you can do it in following way:
<syntaxhighlight lang='js'>
var firstName = dynamicEntity.firstname;
</syntaxhighlight>
Or:
<syntaxhighlight lang='js'>
var city = dynamicEntity.properties["address1_city"];
var city2 = dynamicEntity.properties.address2_city;
</syntaxhighlight>
This way you can access properties of an existing entity instance very easily. If you perform any modifications, you might want to save the record with the save method from JSBridge. In the next example, we demonstrate how to use all above-mentioned methods and properties, and also how to create a reference (lookup) for the parent entity.
Code:
<syntaxhighlight lang='js'>
{
   // Create reference for associated contact account
   var refer = new MobileCRM.Reference("account", accountId);
   var account = new MobileCRM.DynamicEntity("account", accountId);
   var props = account.properties;
   var contact = new MobileCRM.DynamicEntity.createNew("contact");
   var firstName = contact.properties["firstname"] = "#New Contact";
   var lastName = contact.properties["lastname"] = "Example";
   contact.properties["fullname"] = firstName + lastName;
   // one way to set the reference for account
   contact.properties["parentcustomerid"] = refer;
   // but also Dynamic entity contains reference, so can be used as lookup
   contact.properties["parentcustomerid"] = account;
   contact.save(
      function (err) {
         if (!err) {
            // store the contact id for further use
            newContactId = this.id;
            alert("Associated Contact created");
         }
         else
            alert(" An Error Has occurred \n" + err);
      }, null);
}
</syntaxhighlight>
In the code above, we have created a contact and associated it to an account. You can see two ways for how to create a lookup and reference the contact. In the save method we have stored the contact ID for future use, like deleting an entity instance which illustrates the example below and where we are using this stored ID.
<syntaxhighlight lang='js'>
function DeleteRecord()
{
   MobileCRM.DynamicEntity.deleteById("contact", newContactId,
      function (res) {
         alert("Record Was deleted");
      },
      function (err) {
         alert("An Error has occurred \n" + err);
      },
   null);
}
</syntaxhighlight>
=== LoadDocumentBody – load attachment and display it on the HTML page ===
A special addition to Dynamic Entity class methods is LoadDocumentBody. Because of different ways of storing the attachments in the mobile application, a dedicated method for loading the attachments from local (and online) storage is provided. With this method, you can load the body of a document – attachment – and e.g. display it on an HTML page. See the following example:
Code:
<syntaxhighlight lang='js'>
function LoadBody()
{
   MobileCRM.DynamicEntity.loadDocumentBody("annotation", anntotationId,
      function (result) {
         // The result argument contains the string with base64-encoded data
         var imgElement = document.createElement("img");
         imgElement.src = "data:image/png;base64," + result;
         attachment.appendChild(imgElement);
      },
      function (err) {
         alert("An Error has occurred \n" + err);
      },null);
}
</syntaxhighlight>
In the code above, we have provided the method loadDocumentBody with Note’s logical name (= annotation) and an existing annotation ID. The success callback will process the result so that it will find the HTML div element with id ‘img’ and inserts an image element containing the loaded data. They are stored as base64 string, therefore that’s specified first. Nothing else is required in fact – the image is then displayed on the page!
== Controlling UI ==
Another core aspect of JavaScript Bridge is the exposure of native application UI to the JavaScript. This means that the Entity Form (in case of an iframe placed on a form as a tab) is available in JavaScript for access, modification and even present change or save handlers.
Additionally, the commands on the form can be accessed and a custom – more powerful handler can be provided via JavaScript. Commands created directly from JavaScript are also available, however, these are only visible on the iframe tab.
Finally, Form Manager allows opening standard application forms (new or edited) for specified entities. This ultimately interconnects the custom and native UI, so e.g. when a custom list in HTML is created, after clicking on an element of this list, a native form for the particular entity is displayed.
=== Entity form ===
Besides data access, Resco JavaScript Bridge can also access the standard [[user interface components|UI elements]] of [[Resco mobile apps]], such as [[form]]s and [[view]]s. The main UI element is the entity form, represented by the MobileCRM.UI.EntityForm class.
This class has many properties for different aspects of the form, such as entity, form, controllers, etc. To see the full list click here: http://www.resco.net/MobileCRM/support-jsbridge.aspx#MobileCRM.UI.EntityForm
With these methods and properties, you can make perform many operations on the form. However, you can’t access the form directly – as usual with Resco JavaScript Bridge, you must request the form object using a callback function with entityForm object. Usually, this is MobileCRM.UI.EntityForm.requestObject.
In the first example, we show how to use the request object function, and the usage of isDirty property. This property comes handy in situations when you have modified or created the data directly on the offline HTML page and you want to indicate that saving is necessary (the form is ‘dirty’). If the property is set to false, the user can close the form and the information entered on the HTML tab is lost.
<syntaxhighlight lang='js'>
function SetIsDirty()
{
   MobileCRM.UI.EntityForm.requestObject(
      function (entityForm) {
         entityForm.isDirty = true;
      },
      function (err) {
         alert("An Error Has Occured " + err);
      },
      null);
}
</syntaxhighlight>
As you can see, at first you have to request the form and you can access it only in the callback function. By setting isDirty to true, we ensure that the user will see the Save/Discard Changes/Cancel dialog. Then we can do the actual saving of the data in the onSave callback (see next chapter).
==== OnChange ====
In the second example, we demonstrate how to effectively use onChange method and how to use FetchXML with Dynamic Entities.
The onChange callback is used to track changes on the form and perform validations, computations, etc. This is the JavaScript equivalent of the onChange rule in Woodford, but without the limitations of Woodford rules ‘scripting language’. The onChange method is triggered when there is a change on the form – when one of the fields has been modified.
{{Note|It is NOT triggered by changes on the other kinds of tabs – map, media, list...}}
It is called whenever a value in one of the fields on the form changes. However, in our case, we only want to perform some code when the changed item is the field which contains the name of a customer. It is very useful that the first argument of our success callback handler is the Entity Form. This means that there is no need to call requestObject to get the form in this case – it is automatically passed as parameter.
<syntaxhighlight lang='js'>
function OrderComplete() {
   MobileCRM.UI.EntityForm.onChange(
      function (entityForm) {
         var changedItem = entityForm.context.changedItem;
         // check if changed item is the customer field
         if (changedItem == "customerid") {
            var customerId = entityForm.entity.properties.customerid.id;
            FetchAsDynamicEntity(customerId);
         }
      },
      true, null );
}
function FetchAsDynamicEntity(customerId) {
   var entity = new MobileCRM.FetchXml.Entity("account");
   // entity.addAttributes();
   entity.addAttribute("emailaddress1");
   entity.filter = new MobileCRM.FetchXml.Filter();
   entity.filter.where("accountid", "eq", customerId);
   var dynamicEntity = null;
   var fetch = new MobileCRM.FetchXml.Fetch(entity);
   fetch.execute( "DynamicEntities",
      function (result) {
         if (typeof (result) != "undefined") {
            for (var i in result) {
               dynamicEntity = result[0];
            }
         MobileCRM.UI.EntityForm.requestObject(
            function (entityForm) {
               entityForm.entity.properties.new_customeremailaddress = dynamicEntity.properties.emailaddress1;
            },
            function (err) { alert(err); },
            null );
         }
      },
      function (error) {
         alert("An Error Has occurred " + error);
      });
}
</syntaxhighlight>
The first function registers our onChange handler. It is a good practice to call it immediately after the HTML/JavaScript is loaded. One important note here: you probably want this code to take effect immediately when the form is opened, so when you add this page to a form tab in Woodford, disable '''Delay Load'''. This option makes sure that the underlying HTML page is loaded only when user clicks on the tab and in this case, it is almost the opposite of what we want. So it is better to disable it and make sure HTML and JavaScript is loaded immediately with the form.
Once the onChange handler is successfully registered, it takes effect when a field is changed. The first thing it checks is if the field which has been modified is the Parent Customer field. As always, we are using logical names, all in lowercase.
<syntaxhighlight lang='js'>
var changedItem = entityForm.context.changedItem;
// check if changed item is the customer field
if (changedItem == "customerid")
</syntaxhighlight>
Then, we take the customer ID and pass it as a parameter to our function. You can notice two things here – at first, the entityForm holds an instance of the entity, so we can access the data directly. Secondly, the form entity is Dynamic Entity, so we can access its properties like this:
<syntaxhighlight lang='js'>
var customerId = entityForm.entity.properties.customerid.id;
var ob = FetchAsDynamicEntity(customerId);
</syntaxhighlight>
Let’s inspect fetch in the custom method FetchAsDynamicEntities. As the name suggests, we use fetch, but the result is an array of dynamic entities we can work with. As a matter of fact, it's a single dynamic entity, because we filter the entities on the customerId specified as a parameter.
<syntaxhighlight lang='js'>
"DynamicEntities",
function (result)
{
   if (typeof (result) != "undefined") {
      for (var i in result) {
         dynamicEntity = result[0];
      }
   MobileCRM.UI.EntityForm.requestObject(
      function (entityForm) { entityForm.entity.properties.new_customeremailaddress = dynamicEntity.properties.emailaddress1;
      },
         function (err) { alert(err); },
         null);
      }
}
</syntaxhighlight>
If the Dynamic Entity was returned successfully with specified properties, we update the email of the selected customer with the content of the field “new_customeremailaddress”.
<syntaxhighlight lang='js'>
entityForm.entity.properties.new_customeremailaddress = dynamicEntites.properties.emailaddress1;
</syntaxhighlight>
==== OnSave ====
Another equivalent of a Woodford rule in JavaScript is the OnSave. Similar to Woodford’s OnSave rule, the JavaScript Bridge’s onSave callback is executed when the form is being saved. However, due to the asynchronous nature of many calls, you might want to call from the onSave handler (like Fetch for getting the records for validation). We have introduced a special mechanism for the onSave callback which enables control over the save validation execution.
In the example below, we have created a validation function, which will check if the email matches the regular expression during the Save process. Again, please note that if you use event handler like OnSave or OnChange it is desired to UNCHECK the delay load, so the handlers are registered immediately when the form is opened and not only when user switches the tab to the IFrame.
<syntaxhighlight lang='js'>
</syntaxhighlight>
<syntaxhighlight lang='js'>
</syntaxhighlight>
<syntaxhighlight lang='js'>
</syntaxhighlight>

Navigation menu