Resco JavaScript Bridge
Resco JavaScript Bridge |
---|
Resco JavaScript Bridge (also known as JSBridge) is a part of the Resco Mobile CRM application (client application) which offers interaction of custom HTML pages with the client application. It provides an API for accessing/modifying the data and controlling the user interface components from within the JavaScript.
This document contains the description of the main principles of the JavaScript Bridge, main features and API areas, as well as a brief description of common errors and debugging guidelines. It covers the very basics and getting started topics like including the JSBridge.js file or recommended HTML page structure. It also contains many examples with their description, but for the full list of all available methods and functions see the JavaScript Bridge Reference document: https://www.resco.net/javascript-bridge-reference/
The main objective of this document is to make you familiar with the JSBridge concepts and showcase the most common functionality on the examples. After reading this document, you should be able to create and debug your own custom pages and include them into the client application, however, the knowledge of HTML and JavaScript technology is recommended at least on a medium level.
Getting started
If you are just starting to explore Resco JavaScript Bridge, consider reviewing also the following resources:
- Webinar Offline HTML in Resco Mobile CRM - Part 1/2 (Intro) Webinar
- Webinar Offline HTML in Resco Mobile CRM - Part 2/2 (Data Access) Webinar
- Debugging offline HTML
- Introduction to Resco JavaScript Bridge Academy
Including the JSBridge.js file
To start using the features described in this document, it is necessary to include the JavaScript Bridge file into the custom page. This is done by simply including JSBridge.js file e.g. like this:
<script type="text/javascript" src="JSBridge.js"></script>
You can always download the latest version of this file at Resco GitHub page:
While every client application version has a corresponding JSBridge version with specific features, we recommend using the same JSBridge.js file as your client app. You can find older versions in GitHub history: https://github.com/Resconet/JSBridge/blob/master/src/JSBridge.js
However, it is not enough to include the above-mentioned line into your page or script, it is necessary that also the file itself is present at the specified location. If you are going to use your pages as Offline HTML, you also need to upload the JSBridge.js file as the Offline HTML. Similarly, if you are accessing these pages online, the JSBridge.js file should be present at the respective location.
My script does not load
Before contacting technical support, see the sanity checklist of the most common reasons why a script is not loaded.
Recommended META tags
When constructing your HTML pages, there are couple META tags that are useful to know and use.
The first one is used on the platforms that utilize Internet Explorer (Windows 7, 8?) and it makes sure that Internet Explorer 9 Document Mode is being used. Without this line, you might get errors when trying to use some of the methods from JavaScript Bridge on these platforms (Typically, a “Javascript Runtime error: ‘MobileCRM’ is undefined” error).
To enable IE9 Document Mode include following line into the page’s HEAD section:
<!-- Activate IE9 document mode, if available -->
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
The second useful tag is for iOS devices. Not using this one won't result in errors (so it is optional if you are going to use it or not), however, it will allow the page to be pinch zoomed, which is undesired for the custom UI components that are typically built on top of JSBridge. Also, the page and its UI might be loaded in smaller size (to fit the screen) which is typically another undesired effect.
To disable pinch zooming and to load the page on iOS in its corresponding size, use the following code snippet in the HEAD section:
<!-- Defined iOS viewport -->
<meta name="viewport" content="initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=false">
Note that using any of those tags on platforms that don’t support them (e.g., Android) doesn’t cause any issues, so it is a good practice to include these on all the pages.
Offline HTML
Resco JavaScript Bridge comes really handy when you want to visualize mobile data in a very specific way or if you need to capture more complex business logic in the application. But it wouldn’t be such a powerful tool without the possibility to include the custom pages into the customization package, so they don’t have to be available always online.
The option to include the pages (and generally any files) into the customization package is referred to as the Offline HTML capability. You can find this section in the app project in Woodford. You can use it to upload local files and include them in your project. Then, during synchronization, these files are downloaded to the device and available also locally – even offline.
As has been mentioned in previous sections, don’t forget to upload the JSBridge.js file if your pages make use of the Resco JavaScript Bridge. Besides that, you can upload any file – HTML pages, JavaScript files, images, etc., and then reference them on an iframe in the project. To distinguish the local offline files, start the URL with file://
and then continue with the path as in Offline HTML directory. For example, if you have uploaded the file Test.html into the root folder of Offline HTML, the URL would look like this file://Test.html
). You can also click Browse to select from files in the Offline HTML folder.
URL parameters
When constructing the URLs for iframes on entity forms, you can use the following syntax to dynamically create the URL from an entity’s properties. When you use an entity field's logical name (all lowercase) in curly brackets, for example {address1_latitude}
, the application replaces the parameter at run time with the content of the field.
If you use the URL file://mypage.html?{name}
and you have a field “name” on the entity, at run time, the following URL could be used file://mypage.html?John
.
Because of platform-specific limitations, we recommend getting the full URL with all the parameters using MobileCRM.Platform.getURL function. Some platforms will ignore the additional parameters, so if you try to access them via standard means, e.g., document.URL you might get different results on different platforms.
All this can be very useful for a dynamic construction of the URLs and for passing parameters. However, there are some limitations.
Due to these limitations, it might be a better idea to access entity properties via the Entity Form object, instead of passing the parameters via URL. While the parameters you can pass are more or less limited to the entity fields and the same fields can be accessed via the entity object on the Entity Form, this is the recommended and cleaner way to do it – see section Entity form for an example.
Limitation of external web pages
The Windows 8.1 version of the app does not support JSBridge.
Web browser component
In Resco mobile apps, HTML pages are displayed by using Web Browser Component or its equivalent on the respective mobile platform. This means that the browser window is displayed in the mobile app with your custom content.
The browser is, in fact, the standard browser core installed on that particular device:
- iOS: WKWebView (default, newer, faster, more secure; similar to Safari browser)
or WebView (a discontinued legacy option) - Android: Android System WebView (using Chrome technology)
- Windows 7: Chromium Embedded Framework or Internet Explorer (a legacy option)
- Windows 10: UWP WebView (with Microsoft Edge rendering engine) or Edge WebView2
So, when creating your custom pages, consider the capabilities of the corresponding mobile browser. To display the content correctly on all platforms, make sure that you are using HTML and JavaScript features supported by all browsers and their versions on the targeted mobile devices.
Another important aspect to consider is that when a page is displayed in Web Browser Component, some of the features might not be enabled. A typical example is Window.Print() – even though this function is supported by many mobile browsers, when the browsers are running in the Web Browser Component (i.e. in Resco mobile application) this feature is disabled.
From the list of unsupported features, it's worth to mention APIs that open some content in the new window or tab and XHR/Ajax requests requiring CORS. All these APIs has to be replaced by Mobile CRM components supplying this functionality via JSBridge.
Unfortunately, there is no universal list of disabled features, commands, or tags – this differs from platform to platform, browser version, OS version, and so on. To be entirely sure, make sure you test the application and the custom content on all the devices.
Note | Just like a regular web browser, the embedded webview can cache files. On full browsers, you usually have a keyboard shortcut to fully reload a page. In Resco mobile apps, restart the app after synchronizing to ensure it's using the latest files. |
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.
- Recommendations
- JSBridge.js is not written as an ES module. As such, it cannot be imported or required by third-party libraries. However, if you include it as an HTML script, the global variable
MobileCRM
should be available. - This may cause problems with linter or similar tools; for example, the one within the create-react-app tool. The solution is to explain the linter that the global variable
MobileCRM
exists. For example:const MobileCRM = window.MobileCRM;
- If you are using Typescript, make sure to include the file JSBridge.d.ts. This ensures that Typescript understands the global variable
MobileCRM
.
A sample integration with a third-party library is described here: React with JSBridge.
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.
var account = getAccount();
account.DoSomething();
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.
getAccount(processAccount);
// 1 – a lot of code can be here
function processAccount(account) {
account.DoSomething();
}
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
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);
- Good
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);
}
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
MobileCRM.UI.EntityForm.onSave(function(entityForm) {
// asynchronous method call
var fetch = new MobileCRM.FetchXml.Fetch(…);
fetch.execute(…);
}, true);
- 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.
Any fetch run through Resco that does not have the "distinct" attribute explicitly defined is assigned with default="true"
. Therefore, using JSBridge Fetch functionality you only get the unique results.
Code:
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);
The JavaScript code creates a query, which corresponds to the following expression in XML:
<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>
Same query can be executed directly as XML, using MobileCRM.FetchXML method as shows the following example:
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
);
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).
var dynamicEntity = new MobileCRM.DynamicEntity("account", accountid);
If you want to access the properties or save them in a variable you can do it like this:
var props = dynamicEntity.properties;
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:
var firstName = dynamicEntity.firstname;
Or:
var city = dynamicEntity.properties["address1_city"];
var city2 = dynamicEntity.properties.address2_city;
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:
{
// 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);
}
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.
function DeleteRecord()
{
MobileCRM.DynamicEntity.deleteById("contact", newContactId,
function (res) {
alert("Record Was deleted");
},
function (err) {
alert("An Error has occurred \n" + err);
},
null);
}
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:
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);
}
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!
Access metadata information
Access to the metadata from JSBridge can serve you to verify what entities are part of the current project and what permissions does the current app user have. In the example below (that you can add to your home screen), there's a button that displays information about the permissions to a particular entity.
The button:
<button id="checkAccountPermissions" onclick="checkAccountPermissions()">Display entity permissions</button>
The script:
function checkAccountPermissions() {
MobileCRM.Metadata.requestObject(function(metadata) {
var my_entity_name = "resco_appointmentquestionnaire"
var my_entity = metadata.getEntity(my_entity_name);
if (my_entity) {
if (!my_entity.isEnabled)
MobileCRM.bridge.alert("The entity " + my_entity_name + " is not enabled in the project.");
else {
// validate CRUD permissions.
var validCRUDStr = "";
if (!my_entity.canCreate())
validCRUDStr += "Create NOT ALLOWED\n";
if (!my_entity.canRead())
validCRUDStr += "Read NOT ALLOWED\n";
if (!my_entity.canDelete())
validCRUDStr += "Delete NOT ALLOWED\n";
if (!my_entity.canWrite())
validCRUDStr += "Update NOT ALLOWED\n";
var msg = validCRUDStr.length > 0 ? validCRUDStr : "CRUD permission all allowed.";
MobileCRM.bridge.alert(msg);
}
} else
MobileCRM.bridge.alert("The entity " + my_entity_name + " is not part of the project.");
}, MobileCRM.bridge.alert);
}
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 UI elements of Resco mobile apps, such as forms and views. 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. See the full list on GitHub.
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.
function SetIsDirty()
{
MobileCRM.UI.EntityForm.requestObject(
function (entityForm) {
entityForm.isDirty = true;
},
function (err) {
alert("An Error Has Occured " + err);
},
null);
}
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.
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);
});
}
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.
var changedItem = entityForm.context.changedItem;
// check if changed item is the customer field
if (changedItem == "customerid")
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:
var customerId = entityForm.entity.properties.customerid.id;
var ob = FetchAsDynamicEntity(customerId);
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.
"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);
}
}
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”.
entityForm.entity.properties.new_customeremailaddress = dynamicEntites.properties.emailaddress1;
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 that checks 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 necessary to disable Delay load, so the handlers are registered immediately when the form is opened, and not only when the user switches the tab to the iframe.
You also need to call your function while loading the offline HTML page, so put window.onload = name_of_function(); in your script.
Once you have the onSave handler in place, you need to understand the mechanism behind the save validation execution. As soon as the onSave callback returns (true or false) the save execution continues and the record is saved (or not). However, imagine a scenario, where you need to use fetch to get some data to perform the validation in onSave. This would include a call for FetchXml Execute and this function requires another callback. But the code in onSave will not wait until the callback is executed and the data is available. It will inevitably run into the return and end the execution!
For this problem – when you want to call another asynchronous method in the onSave callback, we have introduced the following mechanism. You can call suspendSave before the async method of your choice to stop the save execution. This will wait until you call resumeSave from any other callback or method. The example below should clarify this better:
function SavingValidation()
{
MobileCRM.UI.EntityForm.onSave(
function (entityForm)
{
///<param name="entityForm" type="MobileCRM.UI.EntityForm"></param>
var emailDetail = entityForm.getDetailView("General");
var emailItem = emailDetail.getItemByName("emailaddress1");
if (!emailItem.value || emailItem.value.length == 0)
entityForm.cancelValidation("Email Is empty.");
else if(validateEmail(emailItem.value) == null)
entityForm.cancelValidation("Email is in incorrect format \n\n Correct format : e.g. anything@whatever.info");
else
{
// Return true to apply all changes
return true;
}
// Return false to ignore all changes
//return false
},
true, null);
}
function validateEmail(email)
{
var mailformat = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
return email.match(mailformat);
}
In the first step, we check if the field with email address contains any character and if it exists. If not, we use the property method cancel validation to stop onSave validation and display an error message.
if (!emailItem.value || emailItem.value.length == 0)
entityForm.cancelValidation("Email Is empty.");
We use this method also when our email doesn’t match the regular expression to display the message in a correct email format. In the situation when email matches to regular expression, we return true, to apply all changes and close the form.
Form commands
So far, we have examined how to access the form and react to the change on an item. Now, let’s take a look at another aspect of the form – the commands (actions). In this section, we show you how to access commands from JavaScript, but also how to create a command in Woodford, while this is a necessary part of the process. The example in the next section shows how to create the commands from JavaScript directly.
Commands can be very useful and can enable certain actions throughout the whole form, on all detail tabs. The main principle is to create a command in Woodford but to define and bind the logic in JavaScript.
For general information about form commands, see Command editor.
Creating commands in Woodford
First, create a custom command in Woodford.
- Select an entity from the Project menu and edit its form.
- Click Edit to open the command editor.
- Click New Command. Provide a name and a label, then click OK.
A command defined like this will be visible on all detail tabs of the form, but won’t be available on associated tabs, iframe tabs, or map tabs.
Now you can add an iframe to the form and upload your HTML/Javscript file.
- Select a tab and click Add IFrame.
- Click Manage and upload your offline HTML files, then return to the iframe configuration.
- Click Browse and select the uploaded file.
- Enter other parameters as needed, then click OK.
In our scenario, we create a command that deletes the associated parent customer for a contact entity as well as the contact itself. We have added a new custom command to the Contact form and prepared following JavaScript code for handling this command.
As you can see in the code below, we are defining a custom onCommand callback. This gets triggered when a command is pressed – and with the first parameter which is the name of the command, we specify that we want this callback to be called only for our command with the name ‘custom_Delete’. The second parameter is the callback function with entity form as a parameter. Our command is created on the Contact entity form so we can find parent customer’s ID for the associated record via entity form’s property ‘entity’. In this situation, our contact is associated with one parent only, but in the case when there would be more records for one contact, you will need to create a fetch with additional filter and pass the results to “delete Item(entityName,id)”. When you have the entity name and correct ID, you can call Dynamic Entity function deleteById to erase the Account record. In our example, after all this is done, we close the current form as follows:
MobileCRM.bridge.closeForm();
Full code:
MobileCRM.UI.EntityForm.onCommand(
"custom_Delete", // Name of custom command created in Woodford
function (entityForm) {
deleteItem(entityForm.entity.properties.parentcustomerid.entityName, entityForm.entity.properties.parentcustomerid.id);
deleteItem(entityForm.entity.entityName, entityForm.entity.id)
close();
},
true, null
);
// delete the record
function deleteItem(entityName, id) {
var entity = new MobileCRM.DynamicEntity.deleteById(entityName, id,
// Delete existing Dynamic entity by Exsting entity ID
function (success) {
if (success != null) {
alert("Update sucess error " + success);
}
else {
alert("Item Was deleted");
}
},
function (err) {
if (err != null) {
alert("Update failed error " + err);
}
else {
alert("Item Was deleted");
}
},
null);
}
function close() {
// Close Form containing the HTML
MobileCRM.bridge.closeForm();
}
Creating command via JSBridge
We continue exploring the commands on the forms and this time, we will show you how to create a command on the iframe tab. We will create three commands to showcase some of the features of JavaScript Bridge – one to scan barcode, another to get the device info and third to update the location.
The big difference between the previous approach and this one is that we are not binding the commands created in Woodford with JavaScript methods, but we are creating new commands directly from JavaScript. These commands are displayed only on the iframe tab.
As for the commands themselves, they show different special features of JavaScript Bridge – you can see how to display a scan barcode dialog and process the result, how to get the device information like operating system or model of the device, and also how to obtain the location information (latitude and longitude).
function CommandExample() {
MobileCRM.UI.ViewController.createCommand(true,
["Scan Barcode", "Get Device Info", "Update Location"],
function (command) {
// Check if command with label name "scan barcode" was clicked
if (command == "Scan Barcode") {
ScanBarcode(command);
}
else if (command == "Get Device Info") {
GetDeviceInfo(command);
}
else if (command == "Update Location")
UpdateLocation();
});
function CheckVersion(command) {
//<param name="command" type="MobileCRM.Configuration"></param>
if (command.applicationVersion < "6.0")
return false;
else
return true;
}
function ScanBarcode(command) {
data.style.display = "none";
if (CheckVersion(command) == true) {
MobileCRM.Platform.scanBarCode(
function (res) {
// Input parameter to function is "array" of data
if (!res || res.length <= 0)
MobileCRM.bridge.alert("result doesn’t contain any data");
else {
// simply display barcode on Html Page
var code = document.createElement("span");
code.appendChild(document.createTextNode(res[0]));
data.appendChild(code);
data.style.display = "block";
}
},
function (err) {
alert("Scan barcode failed due to \n " + err);
},
null);
}
}
function GetDeviceInfo(command) {
MobileCRM.Configuration.requestObject(
function (config) { // Input paramter is object , with configuration properties
var setting = config.settings;
var span = document.createElement("span");
span.appendChild(document.createTextNode("Absoulte URL : " + setting.absoluteUrl));
span.appendChild(document.createElement("br"));
span.appendChild(document.createTextNode("Device Info : " + setting.deviceInfo));
span.appendChild(document.createElement("br"));
span.appendChild(document.createTextNode("Application Edition : " + config.applicationEdition));
span.appendChild(document.createElement("br"));
if (config.isOnline == true)
span.appendChild(document.createTextNode("Is Online : True"));
else
span.appendChild(document.createTextNode("Is Online : False"));
span.appendChild(document.createElement("br"));
if (config.isBackgroundSync == true)
span.appendChild(document.createTextNode("Is BackGroundSync : True"));
else
span.appendChild(document.createTextNode("Is BackGroundSync : False"));
span.appendChild(document.createElement("br"));
data.appendChild(span);
data.style.display = "block";
},
function (err) {
alert("An error has occured \n " + err);
},
null);
}
function UpdateLocation() {
MobileCRM.Platform.getLocation(
// If device supports it, create object with prop. lat and long.
function (res) {
//This result is object with properties latitude and longitude
if (!res || res.length <= 0)
alert("No info about location");
else {
var latLong = document.createElement("p");
latLong.appendChild(document.createTextNode("Latitude : " + res.latitude));
latLong.appendChild(document.createElement("br"));
latLong.appendChild(document.createTextNode("Longitude : " + res.longitude));
data.appendChild(latLong);
data.style.display = "block";
}
},
function (err)
{
alert("Get Location INFO failed due to \n" + err);
},
null);
}
}
Form manager
An interesting feature of Resco JavaScript Bridge is the ability to show standard (native) UI forms from JavaScript. This is especially useful if you don't want to recreate the whole UI in HTML, but you want to switch back and forth between HTML to standard UI. There are three Form Manager functions available for this:
- opening new entity dialog (edit form for a new entity)
- entity detail dialog (contact information form)
- entity edit dialog.
In the following example, we create an HTML page that you can add to your home. It includes simply two buttons: one opens the account form and displays one record, the other opens a new contact form.
function OpenAccountEditForm() {
var entity = new MobileCRM.FetchXml.Entity("account");
entity.addAttributes();
var fetch = new MobileCRM.FetchXml.Fetch(entity, 1); // fetch only one record.
fetch.execute(
"DynamicEntities",
function(res) {
if (res && res.length > 0) {
MobileCRM.UI.FormManager.showEditDialog(res[0].entityName, res[0].id);
}
}, MobileCRM.bridge.alert);
}
function openNewAssociatedContactForm() {
var entity = new MobileCRM.FetchXml.Entity("account");
entity.addAttributes();
var fetch = new MobileCRM.FetchXml.Fetch(entity, 1); // fetch only one record.
fetch.execute(
"DynamicEntities",
function(res) {
if (res && res.length > 0) {
var target = new MobileCRM.Reference(res[0].entityName, res[0].id, res[0].primaryName);
var relationShip = new MobileCRM.Relationship("parentcustomerid", target, null, null);
MobileCRM.UI.FormManager.showNewDialog("contact", relationShip);
}
}, MobileCRM.bridge.alert);
}
Our HTML looks like this:
<h3>Welcome to form manager demonstration</h3>
<button id="OpenAccountEditForm" onclick="OpenAccountEditForm()">Open some account</button><br /><br />
<button id="openNewAssociatedContactForm" onclick="openNewAssociatedContactForm()">Add associated contact</button><br /><br />
EntityList
References to objects and methods that we work with:
- EntityList
https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_EntityList - Handle EntityList change event
https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_EntityList_onChange - Request EntityList object
https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_EntityList_requestObject
Create a view with iframe for EntityList in Woodford
At the beginning of working with the entity list object, we have to create and bind an iframe to an existing or a new view. In our example, we create a new view on the Account entity.
- Edit an app project in Woodford.
- On the Project menu, select Account, then click Show UI.
- Click New View and create an empty view:
- Click iFrame and add an HTML file to the view:
What does the parameter Provides data source mean?
- Check it if you want that your HTML retrieves a fetch with entity result to be displayed in the view.
- Clear it to use the original data source defined in the view. You will not be able to change it using a fetch.
- In both cases, the script is properly attached if the URL address is correct. It is good to know that all methods work except those that set the data source.
Using EntityList object and its methods
In this example, we discuss and explain scenarios that can help you understand the logic and use of the mentioned objects. We already know how to add iframes to views. Let's assume that we have a script bound to the Contact entity view.
Retrieve fetch data source and create view
Our first scenario loads data by using the fetch method and sets an additional filter for the retrieved data. We use only the basic functions included in every fetch. This is the easiest way to modify your entity list source.
The code from reference page: https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_EntityList_setDataSource
In the example, we can see that we can modify the fetch source by adding a filter or basic fetch methods. All these inputs create an array result that represents our source.
Note | It is very important to make sure that all the properties which we filter or display are enabled and we display only those which are bound on the row. |
Commands
Entity views contain entity lists that are represented by rows. Each row can contain one or more row commands (buttons). Next command which we call primary command is part of view, as well the multi-selection command. Commands are created in Woodford, except primary command. This one is created in the background of the code. The entity list also allows the same command handling as on the EntityForm.
- List Buttons – Each row in the list contains this button. In our scenario, we create a custom command that takes a photo attachment annotation for the currently selected entity.
Woodford setup:
- Edit a view in Woodford.
- Click Buttons, then New Command.
- As Name enter createNotePhoto.
- As Label enter Photo.
- Click OK.
Now we can insert the logic into an iframe that creates the annotation. This operation requires that the user has the permission to take a picture and code to use DocumentService object and access to files.
The most important part of the code and this section is to register a handler that listens for a specific command name. To run logic when the command is executed, we need to use callback. In this callback, we retrieve an entity list object with all properties and methods available. In a situation like this, we need to access the selected record from the list.
Code:
MobileCRM.UI.EntityList.onCommand("custom_createNotePhoto", function (entityList) {
/// <param name='entityList' type='MobileCRM.UI.EntityList'/>
/// selected record in list.
var regarding = entityList.context.entities[0];
var doc = new MCRMdocument();
doc.capturePhoto(regarding);
}, true, null);
The code above shows how to register the command handler and access the selected record. In the code, we can see another important thing: the entityList contains an array of context entities. In our case, we have selected only one record, since we work with the row button. On the other hand, we can access multiple records, which is achieved by selecting the use of multi-select option.
Next part of code is developed as an example, it requires understanding document service object.
DocumentService:
https://www.resco.net/javascript-bridge-reference/#MobileCRM_Services_DocumentService
The rest of the code, class of MCRMdocument:
var MCRMdocument = function () {
this._service;
construnctor:(this._service = new MobileCRM.Services.DocumentService());
MCRMdocument.prototype.capturePhoto = function (regarding) {
/// <param name='regrding' type='MobileCRM.Refernce'/>
if (this._service != undefined) {
this._service.capturePhoto(function (fileInfo) {
this.getDocumentBody(regarding, fileInfo);
}, MobileCRM.bridge.alert, null);
}
};
MCRMdocument.prototype.selectPhoto = function (regarding) {
/// <param name='regrding' type='MobileCRM.Refernce'/>
if (this._service != undefined) {
this._service.selectFile(function (fileInfo) {
this.getDocumentBody(regarding, fileInfo);
}, MobileCRM.bridge.alert, null);
}
};
createAnnotation = function (regarding, fileInfo, documentBody) {
/// <param name='regrding' type='MobileCRM.Refernce'/>
/// <param name='fileInfo' type='MobileCRM.Settings._fileInfo'/>
/// <param name='documentBody' type='base64'>File base 64 string.<param>
var note = MobileCRM.DynamicEntity.createNew("annotation");
if (fileInfo) {
var splits = fileInfo.filePath.split("\\");
note.properties.filename = splits[splits.length - 1];
note.properties.mimetype = fileInfo.mimeType;
note.properties.isdocument = 1;
note.properties.documentbody = documentBody || " ";
}
note.properties.subject = "Document";
note.properties.notetext = "Test attachment from document service";
note.properties.objectid = regarding;
note.save(function (err) {
if (err) MobileCRM.bridge.alert(err);
else {
// display newly created annotation
MobileCRM.UI.FormManager.showEditDialog(this.entityName, this.id);
}
});
};
getDocumentBody = function (regarding, fileInfo) {
/// <param name='regrding' type='MobileCRM.Refernce'/>
/// <param name='fileInfo' type='MobileCRM.Settings._fileInfo'/>
MobileCRM.Application.readFileAsBase64(fileInfo.filePath, function (base64) {
this.createAnnotation(regarding, fileInfo, base64);
}, MobileCRM.bridge.alert);
}
}
Another example of the native command handler Call. The steps are the same as before, we just need to bind iframe on the Entity view and create handler for specific command with the exact name.
In the next example, we take the entity list context property of the currently selected record and display a message box with those properties. Once the user selects the option, we can make a call with the desired option.
Code:
var cmd = "Call";
MobileCRM.UI.EntityList.onCommand(cmd, function (entityList) {
/// <param name='entityList' type='MobileCRM.UI.EntityList'/>
var msg = new MobileCRM.UI.MessageBox("Make a call?", "Cancel");
var currentRec = entityList.context.entities[0];
msg.items = [currentRec.fax, "some another option"]; // here you have to set your option of message box (buttons)
msg.show(function (number) {
MobileCRM.Platform.makeCall(number, MobileCRM.bridge.alert, null);
}, MobileCRM.bridge.alert, null);
}, true, null);
How it looks in the app:
Primary command
This type of command overrides the primary entity list view command. It can be created only in the background of the code, from a script. Its handler is executed after this button is clicked.
Screenshot from the app:
In our example, we just override the basic button with some additional options. It means that we create an entity record of the entity type, by using predefined fields values.
Code:
MobileCRM.UI.EntityList.setPrimaryCommand("Create", function (entityList) {
/// <param name='entityList' type='MobileCRM.UI.EntityList'>
MobileCRM.UI.FormManager.showNewDialog("account", null, {
"@initialize": {
telephone1: 555 666 777, // new contact will have this phone
address1_line1: "Radmond 71", // ... and address too
address1_city: "Los Angeles"
}
}, null);
MobileCRM.bridge.alert("Test!");
}, null);
Multi Select command
The command allows users to select more than one record. The entity list object contains a method that handles these commands. It is the same as the first example scenario. We need to use the onCommand method with a valid command name.
In our scenario, we iterate through the selected entities and compare them with the values of predefined entities. It is a very simple validation for multi-selection on entity list.
Woodford setup:
- Edit a view in Woodford.
- Click Multi Select, then New Command.
- As Name enter multiselect.
- As Label enter Selected.
- Click OK.
Code:
MobileCRM.UI.EntityList.onCommand("custom_multiselec", function (entityList) {
/// <param name='entityList' type='MobileCRM.UI.EntityList'>
var predefinedNames = ["!Active Cycling", "Basic Bike Components"];
var selectedEntities = entityList.context.entities;
var exist = false;
predefinedNames.forEach(function (val, i) {
selectedEntities.forEach(function (sel, j) {
if (sel == val) {
exist = true;
break;
}
});
if (exist == false)
MobileCRM.bridge.alert("Selected entity is not in predefined array.\n" + val.properties.name);
});
}, true, null);
},
This was a sample scenario for how to work with commands on the entity list. These methods and functions allow users to manipulate the data and handle command execution. It is very useful to be able to implement your custom logic if it is required.
Event handlers on entity list
The entity list object contains events that can be explicitly called and handled by specific methods. Native Entity list allows users to edit fields, select them, call buttons, etc. In this section, we discuss how to control and use those options in sample scenarios.
Our sample scenario looks as follows: we have one field fax on our row and we want to handle when this field is changed to validate input.
Woodford setup: Make sure to set Kind to Text-Edit to make it editable.
Now we must register the onChange event handler to allow us to control what value the user writes to the fax field. In our case, we just check if the value is not empty. If yes, we explicitly set a default value.
MobileCRM.UI.EntityList.onChange(function (entityList) {
/// <param name='entityList' type='MobileCRM.UI.EntityList'/>
var context = entityList.context;
if (context.propertyName == "fax") {
var editedEntities = entityList.context.entities;
var props = editedEntities[0].properties;
if (!props.fax)
props.fax = "554 321"; /// value by default.
}
}, MobileCRM.bridge.alert, null);
In the previous example, we only handle changes on entity list row, but if you know what row and property you need directly to edit or edit and save immediately, then check out the following method, which performs exactly this action.
Reference to the code:
https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_EntityList_setEntityProperty
In the next example, we follow a similar scenario as in the previous example, except for the onSave event handler and set ‘errorMessage’ property in case of validation fail. See this example and the code snippet on the reference page for more. The onSave handler works the same on entity form and another part of the application where this handler can be registered.
Reference to code:
https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_EntityList_onSave
Entity list click event
This part tells more about the click event and its context. Imagine that we want to handle clicking on the entity list, on any row or cell on it. This event needs to contain some property that describes entities, property name, and event definition.
- Entities – It represents the tapped dynamic entities. In the code, we use an array for definition, each element of it is any tapped entity.
- Property name – The field name that was clicked within the list item.
- Event – Definition for an event. EntityListClickEvent object describes what action has been done while specifying the cell, row and binding event value.
- Action that the event can describe can be found here: https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_EntityListCellAction.
This event can be used in many scenarios. In most cases, you just want to know what cell/row was selected and which entity fields were modified. For further detail, please visit our reference page.
Global events
References and objects what we use in this document as prerequisites
- Raise global event
- A method to raise a global event, which can have the listener bound by another iframe.
- https://www.resco.net/javascript-bridge-reference/#MobileCRM_Bridge_raiseGlobalEvent
- Listen on a global event
- A method that listens on a risen event from any iframe.
- https://www.resco.net/javascript-bridge-reference/#MobileCRM_Bridge_onGlobalEvent
Prerequisites objects are dependent on each other. They work as simple event listeners and handlers similar to other programming language Events logic. Each method requires the specified event name, handler, and a bind property.
- From the name of the input method’s parameter, it is obvious that the event name represents the name of the event you raise or listen on.
- A handler is a type of function, a callback in case of an event was fired or raised for listening.
- Bind property meaning is the same as for another HTML/JavaScript event handlers. If you want to register events to listen to, you have to bind it. To unregister events, just set bind property to false.
Events predefined in the application
Resco mobile apps contain many events, some of which can be handled by your custom HTML, JavaScript code. To listen on those events, you need to register an event listener with the specific event name. Here are the available pre-defined events of MobileCRM application:
- EntityFormClosed
- This event is triggered after an event form is closed. Remember that not every form is an entity form. We also have iFrame and HomeForm.
- Object: https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_EntityForm
- Sample scenario: We work on an account entity form, that contains an iframe bound with a listener on this method. After this event is registered, we can rely on the handler callback being triggered when any opened entityForm is closed. For our scenario, imagine that we opened some corresponding order entity form, edited this entity, and tapped on the close button. Suddenly, this handler will listen on it.
- IFrameFormClosed
- Event handler similar to the previous EntityFormClosed, the only difference is in the type of object.
- Object: https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_IFrameForm
- SyncStarted
- For this event I will start with a scenario that suits this event the most.
- Sample scenario: Since this event is triggering in various situations, most of time we want to handle it from the application start. In this special case, we have to register this event during an application opening. The question is: How to bind iframe with a handler to the homeForm? Answer: We have only one option, to place the HTML file to the first position of home items. If this happens, the application replaces the native UI with your custom HTML.
Note | You have to name this iframe only as UIReplacement or HomeReplacement. Other names of iframes and their content including the script part are ignored until you directly open them. |
- From this moment, when you publish the project and your customization didn’t contain iframe with this name, you will be asked to restart your application to apply UI Changes. At this moment we replace whole HomeForm UI with your HTML. In this situation, you will probably work with this object and your script will implement the whole logic to display/open/close items of the home screen.
- Back to our example, we register handlers for SyncStarted event and hide the UI replacement to continue working with the native UI, for example like this:
window.onload = function () { /// <summary>Method executed when all Html DOM components are loaded.</summary> init(); } function init() { MobileCRM.bridge.onGlobalEvent("SyncStarted", function (args) { /// <summary>Register event handler for pre-defined event.</summarry> /// <param name='args' type='Object'>Handler callback arguments.</param> MobileCRM.bridge.alert("Synced Started"); }, true, null); // hide UI Replacement MobileCRM.UI.HomeForm.hideUIReplacement(); }
- From this moment, every time the synchronization starts, this alert is displayed. Of course, this is just one of many examples of how to use this predefined event. This just shows how to control the state of an application when the synchronization starts. This handler is not restricted for use on the home form only. You can use it on another iframe bound in available places like entity form, iFrameForm or entity list.
- SyncFinished
- A predefined event that works similarly to previous SyncStarted, except the state when it is triggered. We can rely on this event when a synchronization finishes.
- The event handler callback carries an object as an argument that we can use to check the synchronization result. See in the following object and sample code:
- Object: https://www.resco.net/javascript-bridge-reference/#MobileCRM_Services_SynchronizationResult
function onSyncFinished(bind) {
MobileCRM.bridge.onGlobalEvent("SyncFinished", function (args) {
var syncResult = new MobileCRM.Services.SynchronizationResult(args.lastSyncResult);
if (syncResult.newCustomizationReady)
MobileCRM.bridge.alert("New Customization ready.");
else
MobileCRM.bridge.alert("Not any new Customization ready.");
}, bind, null);
}
- As in the previous handler, we are able to listen on this event anywhere from the bound iframe’s possible placing.
- Sample scenario: Imagine that we create a record in the mobile app, but we want to be sure that while we did so, nothing new was added to the project, if customization was not changed already. So we will handle the saving event of this record with callback, which will invoke synchronization from our script. Since we have a listener for the global SyncFinished event, we will check if any new customization is not ready. Depending on our result we can continue or decline operations made on record.
SyncFinished handler
In a specific scenario, your application requires to use UIReplacement and its sync finished event. We are talking about the case when your app has been synced and downloaded anew customization. In this case, a modal window appears Application was updated. This scenario does not trigger the global event syncFinished, because this is a special event on home form. Look at the following code snippet that shows how to write this event. The implementation of this code is the same as with global syncFinished event.
Reference:
https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_HomeForm_onSyncFinished
Custom global event
This section describes the custom event that we handle. The important method here is the same one as in the previous explanation. The only difference is the parameters we send. The name of the event is our custom event name that we trigger and listen on. Handlers contain the logic that we need to implement when the event triggers or was triggered. Arguments that the handlers carry can be a type of object, it could mean anything since you create and handle them by your logic.
Unregister the events
In this case, when we don’t want to listen on this event, we have to call the same method, but the parameter bind must be set to false. This scenario can be described as += and -= in other programming languages.
Lookup
References and objects that are used for lookup creation:
- LookUp Form – https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_LookupForm
- Multi LookUp Form – https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_MultiLookupForm
- DetailView Lookup link Item – https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_DetailViewItems_LookupSetup
All the mentioned objects can be used for different approaches. Some of them are dependent on an object that holds reference and creates LookUp. That doesn’t mean you can’t call the other ones from those objects.
Create lookup view in Woodford
Woodford allows you to create views designed to be used in lookups. In this example, we create a new lookup view for the Account entity.
- Edit an app project in Woodford.
- Select Entities > Account from the Project menu and click Show UI.
- Click New View and enter the following parameters, then click OK.
Independent lookup
This kind of lookup can be used from forms, views, home screen, or HTML files. It doesn’t require the use of detail view object, so you can create a lookup or multilookup from an HTML file and set what view you want to see. The best part is adding a FetchXML filter to view.
Each lookup object mentioned in the first section contains a method to add FetchXML filter. Please check this reference to find out more about what method we talk about. The method is called addEntityFilter. Keep in mind that the fetch filter simply adds another filter that is described by fetch.
https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_MultiLookupForm_addEntityFilter
Another important thing to know is how to define views available for selection in the app.
For this, we can use addView method. Each view that we add using this method will be available in the selector.
https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_LookupForm_addView
Normal LookUp form
This is the normal lookup form that displays one view form. We use it to select one unique value for the lookup.
The code from reference page: https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_LookupForm_show
MultiLookUp form
Unlike in a normal lookup, users can select multiple items in a multilookup form. We receive the selected items in a success callback. After tapping the back button, the callback is executed. If all operations were successfully done, we get callback to success, otherwise to failure.
Custom M:N relationship MultiLookup form
If a user has entities that can represent an M:N relationship, this lookup is the best way how to achieve this. The behavior of lookups can be almost completely controlled by JSBridge.
For example, we start from one entity (Appointment). We want to create a display in this lookup on the right side that doesn’t have any reference to appointments. It is just an in-the-middle ‘Table’ between M:N relationship entities, but we need to use the middle point data from appointment to filter correct data in right side of the lookup. We are able to add entity filter that will use in fetch this appointment's values. For example, use the starts_on field in the filter.It can look like this.
var multiLookup = new MobileCRM.UI.MultiLookupForm("middleEntityName");
var filter = '<fetch version="1.0">' +
'<entity name="middleEntityName">' +
'<condition attribute="starts_on" operator="on-or-after" value="' + appointment.startson + '" />' +
'</entity>' +
'</fetch>';
You might run into this problem: Since we create an M:N relationship and this entity doesn’t know anything about appointments, it is not easy to set up what we want to see on the right side. In our case, it is the third object that is created with the use of the selected values and contains a reference to an appointment. So the best way is to set the data source by using an array of references of the third type entity object, which refers to this appointment.
Type: https://www.resco.net/javascript-bridge-reference/#MobileCRM_Reference[]
The code from reference page: https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_MultiLookupForm
For example, the mentioned LookUp can be executed by using the button command in your HTML files.
Dependent LookUp
This type of lookup is dependent on DetailView object, since we are talking about detail items that we defined as lookup fields. Currently, we have two types of lookup – opened in form or directly inline. Now we will describe the functionality for the type of detail view item lookup.
DetailViewItem LookUp – From the name of the object, we can expect that it requires a detail view and a detail item. This lookup is bound on a lookup field item. For example, we are on the Contact entity form and the first element is an Account lookup field. Using this object we can create a custom filter and a view that is displayed when a user taps on this item. We have two types of lookup that we can bind on this item. The first is inline, the second displays a lookup form as mentioned before.
Inline lookup
An example of an inline lookup is listed below. It uses methods like a normal lookup, and a custom filter based on an XML view.
var customXMLView =
'<fetch version="1.0">' +
'<entity name="account">' +
'<filter type="and">' +
'<condition attribute="statuscode" operator="ne" value="2" />' +
//'<condition attribute="address1_city" operator="eq" value="Bratislava" />' +
'</filter>' +
'<link-entity name="contact" alias="L0" from="parentcustomerid" to="accountid" link-type="inner">' +
'<filter type="and">' +
'<condition attribute="fullname" operator="eq" value="Custom contact name" />' +
'</filter>' +
</link-entity></entity></fetch>';
/// Create inline lookup setup
inlineSetup = new MobileCRM.UI.DetailViewItems.LookupSetup();
LookUp.entities.push("account");
inlineSetup.addFilter("account", customXMLView);
var dialogSetup = new MobileCRM.UI.DetailViewItems.LookupSetup();
Dialog lookup
Dialog lookups open normal lookup forms.
/// Create dialog setup what behaves like LookUp form
dialogSetup.addView("account", "Default", true);
As we already mentioned, we have to get a detail view and bind these items. For this, we use EntityForm request object. The final phase is placing the items to specific positions. Refer to the link below to learn about the possibilities you have and parameters that need to be set if you use just dialog/inline lookup.
MobileCRM.UI.EntityForm.requestObject(function (entityForm) {
/// <param name="entityForm" type="MobileCRM.UI.EntityForm"/>
var dv = entityForm.getDetailView("General");
/// …. CODE OF ITEMS HERE
/// Add lookups to specific position on view.
dv.updateLinkItemViews(0, dialogSetup, inlineSetup, false);
Link: https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI__DetailView_updateLinkItemViews
Questionnaire
Use Woodford to add offline HTML content to questionnaires. See Managing offline HTML content for more information.
Use Questionnaire Designer to assign files to questionnaires. Edit a questionnaire template, click Options and on the Rules tab, link to the file in the property Script Path. For offline files, use the file://
prefix, for example: file://test_questionnaire.html
.
If you don't specify the Script Path, the app will try to search for an HTML file in the following path: file://Questionnaire/{name of the questionnaire}.html
.
How to write scripts for the questionnaire by using JSBridge API
The questionnaire form contains properties and methods to access and manipulate questions and question groups. The basic handlers like onSave, onChange are implemented as well.
In this section, we explain some basic operations. The object what we use can be found on this reference page:
- Basic questionnaire form object: https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_QuestionnaireForm
- Questionnaire group: https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_QuestionnaireForm_Group
- Question: https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_QuestionnaireForm_Question
Methods can be found in the section with Functions. We explain the basic handlers like onSave, onChange, and requestObject (i.e. onLoad).
Access questionnaire data, set properties
The first example demonstrates how to access the data on the questionnaire form and set the property of groups and questions.
MobileCRM.UI.QuestionnaireForm.requestObject(function (qForm) {
/// <param name='qForm' type='MobileCRM.UI.QuestionnaireForm' />
/// Get the target questionnaire entity of the form.
var target = qForm.relationship.target;
var escape1_group = qForm.findGroupByName("escape-routes-and-exits");
/// Disable group if exist
if (escape1_group) {
escape1_group.isEnabled = false;
}
var extinquisher_qeustion = qForm.findQuestionById("9e4a9763-aa5d-4dd2-80aa-6dc940e388cf");
if (extinquisher_qeustion) {
extinquisher_qeustion.label = "New label set using JSBridge api";
}
var city_question = qForm.findQuestionByName("city");
if (city_question) {
/// set the description value of the question during onload
city_question.description = "If city will not be valid, error message will be set";
}
/// base form object of questionnaire
var f = qForm.form;
}, MobileCRM.bridge.alert, null);
As we can see from the code above, we can request the main object of the questionnaire form. It works the same as on another entity form or entity list.
We can get the reference to the parent entity that opens the questionnaire. It can be used in the case when the regarding entity must refer to a parent.
The next part contains the method to access groups and questions directly by their name or ID.
References:
- https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_QuestionnaireForm_findQuestionById
- https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_QuestionnaireForm_findGroupByName
- https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_QuestionnaireForm_findQuestionByName
If we get the group, we can set the property of this object. In our case, we disable the whole group. You can set the visibility of the group as well. We can set these properties also on the question if it's available. We changed the description. In the application, it looks like this.
Work with handlers
A simple onChange handler sets an error message and validation on specific question.
MobileCRM.UI.QuestionnaireForm.onChange(function (qForm) {
/// <param name='qForm' type='MobileCRM.UI.QuestionnaireForm' />
if (qForm.context !== undefined) {
var changedItem = qForm.context.changedItem;
var changeItem_newValue = qForm.context.newValue;
var qName = "are-all-emergency-light-indicators-illuminated-if-present";
if (changedItem === qName && changeItem_newValue === false) {
var emergency_question = qForm.findQuestionByName(qName);
if (emergency_question !== null) {
/// set validation and error message
emergency_question.errorMessage = "Emergency light can't be illuminated";
emergency_question.validate = true;
}
}
}
}, true, null);
An important part of the code is in the second and third lines. They show how to access the changed item. Anytime a question changes, this handler triggers. Therefore, it is good to know how to determine what question has been changed.
The last part shows how to set the property validation and custom error message for the changed item, if the value was set to false. In the application, it looks like this.
The error message is displayed when a user attempts to close the form.
OnSave event
This example shows how to handle the onSave event. It cancels validation and discards the error message if the value of the question is correct.
MobileCRM.UI.QuestionnaireForm.onSave(function (qForm) {
/// <param name='qForm' type='MobileCRM.UI.QuestionnaireForm' />
var qName = "are-all-emergency-light-indicators-illuminated-if-present";
var emergency_question = qForm.findQuestionByName(qName);
if (emergency_question.validate) {
if (emergency_question.value === true)
emergency_question.errorMessage = undefined; // disable error message
else
qForm.cancelValidation("Emergency light can't be illuminated");
}
return true;
}, true, null);
Repeatable groups
Two events handle working with repeatable groups:
- When a group is repeated
- When a group is deleted
MobileCRM.UI.QuestionnaireForm.onDeleteGroup(
function (qForm) {
var deletedGroupId = qForm.context.group;
var deletedGroup = qForm.groups.find((g) => g.id === deletedGroupId);
MobileCRM.bridge.alert("Deleted group: " + deletedGroup.label);
},
true,
null
);
MobileCRM.UI.QuestionnaireForm.onRepeatGroup(
function (qForm) {
var newGroupId = qForm.context.newGroup;
var sourceGroupId = qForm.context.sourceGroup;
var newGroup = qForm.groups.find((g) => g.id === newGroupId);
var sourceGroup = qForm.groups.find((g) => g.id === sourceGroupId);
MobileCRM.bridge.alert(
"Repeated group: " + newGroup.label + " from " + sourceGroup.label
);
},
true,
null
);
Route plan
You can use HTML and JavaScript to extend route plan beyond its out-of-the-box functionality. The following events can be handled:
- Item Added - receiving added entity
- Item Removed - receiving removed entity
- Item Completed - receiving entity to be completed and status to be set. You can use it to validate an appointment before closing. If validation fails, completion can be canceled and a custom error message displayed to the user.
- Item Saved - receiving entity before it is saved
- Item PostSaved - receiving entity after it is saved
- Route Saved - receiving all entities in the route before they will be saved (user clicked the Save button or other internal trigger)
- Route PostSaved - receiving all entities in the route after they were saved.
- Route Reloaded - triggered every time the records in route are reloaded, e.g. in case they are reordered, added, removed, completed or optimized. Receiving all entities and additional info such as route day.
You can find information and examples on our Resco GitHub.
UI (Home) replacement
You can use HTML and JavaScript to replace the user interface of the home screen.
References and objects that we use:
- Here you can find the attributable functions to this main object. Later we will use each one of them.
- Synchronization result – https://www.resco.net/javascript-bridge-reference/#MobileCRM_Services_SynchronizationResult