JSBridge Guide - Resco Mobile CRM
Support


Table of Contents

Introduction

   Including the JSBridge.js file

   Recommended META tags

   Offline HTML

   URL parameters

   External web pages – limitation (Windows 8.1 app version – not supporting JSBridge)

   Web Browser Component

   External and 3rd party HTML/JavaScript libraries

Woodford

Asynchronous approach

   Common errors

Debugging the Offline HTML solution

   Windows Desktop/Store

      Visual Studio Integration

   Android

   iOS

      Installing the Debug Build on iOS Simulator

      Installing the Debug Build on iPhone/iPad

      Preparing the Debugging Environment

   Changing the local copy of the Offline HTML files

   Overriding the Offline HTML root

Data access API

   Fetch

   DynamicEntity

      LoadDocumentBody – load attachment and display it on the HTML page

Controlling UI

   Entity Form

      OnChange

      OnSave

   Form Commands

      How to create a command in Woodford

      Creating command via JSBridge

   Form Manager

EntityList

   References to objects and methods that we work with

   Create a view with IFrame for EntityList in Woodford

   Sample scenario shows how to use EntityList object and its methods

      Retrieve fetch data source and create view

   Commands

   Event handlers on entity list

      Entity list click event

Global Events

   References and objects what we use in this document as prerequisites

   Application’s pre–defined events

   SyncFinished handler

   Custom global event

   Unregister the events

LookUp

   References and Objects that are used for look up creation

   Create view template for Lookup in Woodford

   Create filter for view’s records with the use of Row Script

   Bind iFrame on Entity Form/Home Form

   Independent LookUp

   Dependent LookUp

Questionnaire

   How to add Iframe on questionnaire form

   How to write scripts for the questionnaire by using JSBridge api

UI (Home) Replacement

   References and Objects that we use

   Set up IFrame in Woodford

   Simple use cases and code snippets

      Access the synchronization result object when sync finished

TypeScript

 

Introduction

Resco JavaScript Bridge (also known as JSBridge) is a part of the Resco MobileCRM 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 UI 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, which you can find in following chapters. 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:
http://www.resco.net/mobilecrm/support/jsbridge.aspx

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.
 

 

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 the following location (or go through Support page on Resco web page): http://www.resco.net/MobileCRM/downloads/JSBridge.js

While every client application version has a corresponding JSBridge version with specific features, it is recommended to use the same JSBridge.js file as your client app. For that, we have made available an archive with all the older versions at this URL: http://www.resco.net/MobileCRM/downloads/JSBridge_Versions.zip

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 (see part also and https://youtu.be/R7GpdC_y17Y) 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.
 

 

Recommended META tags

When constructing your HTML pages, there are couple META tags which 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), but 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 Offline HTML capability. You can find this section in the mobile project in Woodford or Resco Advantage management console where a simple file explorer window allows you to upload the local files and include them into the package. Then, during synchronization these files are downloaded and available also locally – or offline.

As has been mentioned in previous sections don’t forget to upload the JSBridge.js file your pages make use of the Resco JavaScript Bridge. Besides that you can upload any file – an HTML page, JavaScript file, image, etc. and then reference it 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 (e.g. if you have uploaded the file “Test.html” into root folder of Offline HTML, the URL would look like this “file://Test.html”). You can also use handy Browse option which allows you to browse files directly in the Offline HTML folder.
 

 

URL parameters

When constructing the Offline HTML URLs for IFrames on entity forms you can use following syntax to dynamically create the URL from entity’s properties. When you use an entity field’s logical name (all lowercase) in curly brackets – “{ … }”, this will be replaced by the application in run time with the content of those fields.

So, if you create a following URL – “file://mypage.html?{name}” and assuming that you have a field “name” on the entity, this will be replaced during runtime with e.g. “file://mypage.html?John”.

Due to the platform specifics it is recommended to get the full URL with all the parameters via 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 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 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.

 

 

External web pages – limitation (Windows 8.1 app version – not supporting JSBridge)

 

 

Web Browser Component

Displaying of the HTML pages within the client application is implemented 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 installed on that particular device – on iOS it will be Safari, on Android Chrome and on Windows devices Internet Explorer. So, when creating your custom pages consider the capabilities of the corresponding mobile browser. Also bear in mind that if you want your pages to be displayed 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.

Unfortunately, there is no list of all the 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.

 

 

External and 3rd party HTML/JavaScript libraries

The abovementioned stuff applies to 3rd party HTML/JavaScript libraries that you might want to use. Resco mobile application does not 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 – 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 3rd party libraries and components including such popular frameworks like jQuery or D3 for data visualization.

 

 

Woodford

In this section, you can find out how to use offline HTML in Woodford.

How to attach IFrame to specific place in application:

  • Add IFrame in Woodford

    JSBridge Guide 001
     

  • Use IFrame as one of home items

    JSBridge Guide 002

    In the application this iFrame will be visible in the home items of home screen.
     

  • Bind iFrame to edit form

    Navigate to specific entity, then select forms and choose between Edit/Detail form.

    Click on iFrame button to display the Properties window.
    JSBridge Guide 003

    You can find more information about Delay Load checkbox in the sections Controlling UI and onChange.

    Visible checkbox provides function hide/show iFrame in application tab bar.

 

 

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 you write the 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 would be an asynchronous method, the code would 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. Similar code, but in asynchronous fashion it would look something like this.

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 would try to do it directly afterwards (on the place of comment // 1) this would result in an error. On the place of the comment // 1 could be an arbitrary amount of code and the callback function can be called anytime in between or afterwards.

See common errors below for a better overview on what misunderstanding of this concept can lead to.

 

 

Common errors

One of the most common errors is using the asynchronous result before the result callback is called. Following snippet shows such situation (see how this can be tricky to spot because of the in-line callback function):

Wrong:

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);

Correct:

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.

Wrong:

MobileCRM.UI.EntityForm.onSave(function(entityForm) {
   // asynchronous method call
   var fetch = new MobileCRM.FetchXml.Fetch(…);
   fetch.execute(…);
}, true);

Right:

Please check the following reference: https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_EntityForm_suspendSave
 

 

Debugging the Offline HTML solution

When creating and testing custom HTML pages and scripts it is often necessary to debug the JavaScript code to see what is actually happening. You can use alerts and logging as a substitute for this option, but in general, debugging gives you much quicker insight into the actual execution.

Depending on the platform you are working on, there are different ways how to enable debugging. Here are described the approaches for Windows (7 and 8.1) and iOS. It is also described how to enable debugging for Android, but you will need a device with Android OS v4.4 at least.

 

 

Windows Desktop/Store

Visual Studio 2012/2013 supports the script debugging in external applications. It allows attaching to the client application (the version for Window 7 and for Windows 8.1 Store) and debugging your offline HTML script code.

Before trying this, you will have to enable the script debugging in Internet Explorer. Open Internet Options/Advanced and uncheck the box “Disable script debugging”.

JSBridge Guide 005

After that, it will be possible to attach the running process “MobileCRM.exe” to the script debugger in Visual Studio. Uncheck all Code Types except the “Script”.

JSBridge Guide 006

Visual Studio provides the comfortable script debugging including the inline editing. To apply the changes, you will need to close the form which contains the web browser. Easiest way to do that is opening the “Setup” or “Sync” dialog.

JSBridge Guide 007

It is also possible to debug the Windows Store version of the client application. The only limitation here is that the online editing is not allowed.

JSBridge Guide 008

 

 

Visual Studio Integration

To make the script debugging even easier, we provide the Visual Studio extension “Mobile CRM Integration” which includes the command “Run in Mobile CRM” into the Visual Studio Tools menu and also provides the single click experience for attaching it to running instance of Mobile CRM.

In addition to that it offers the functionality of overriding the Offline HTML root to your solution folder. See the section “Overriding Offline HTML root” for further details.

JSBridge Guide 008

 

 

Android

Android supports debugging of the web pages opened in third-party applications, by using a remotely attached Chrome inspector. It’s supported since Android 4.4 (KitKat).

Mobile CRM application doesn’t support debugging by default for security reasons. However, it’s possible to switch it on by calling the method “enableDebug” from your Offline HTML solution. See the following link for details:

https://www.resco.net/javascript-bridge-reference/#MobileCRM_Bridge_enableDebug

Developers can use the JSDev edition of the Mobile CRM app which supports the debugging by default. It’s available at:

https://www.resco.net/downloads/DebugBuilds.zip

In addition to enabled debugger, this edition supports the Offline HTML overriding (see below).

To learn more about the Android remote debugging, take a look at Google tutorial:

https://developers.google.com/chrome-developer-tools/docs/remote-debugging

 

 

iOS

iOS also supports debugging of the web pages opened in third-party applications. However, apps from the AppStore (or even from the TestFlight) are sandboxed which prevents development tools connecting to them. That’s why we started to provide the special installation that can be used for development purpose. It’s available at:

https://www.resco.net/downloads/DebugBuilds.zip

 

 

Installing the Debug Build on iOS Simulator

To install the debug build on iOS Simulator, follow these steps:

  • Open Xcode menu Xcode/Open Developer Tool/iOS Simulator
  • Open Terminal, go to DebugBuilds/iPhoneSimulator directory and type in the following command:
    xcrun simctl install booted MobileCrm.app

 

 

Installing the Debug Build on iPhone/iPad

To install the debug (JSDev) build, follow these steps:

  • Create an iOS Developer account at https://developer.apple.com/
  • Create a Wildcard App ID for bundle ID “*” (Identifiers/App IDs).
  • Create an iOS app development provisioning profile for this AppID and download it (as Wildcard.mobileprovision file).
  • Log in to your account from Xcode (Preferences/Accounts).
  • Create an iOS Development signing identity (View Details button).
  • Download iResign tool from https://www.resco.net/downloads/iReSign.app.zip
  • Unzip it and run it.
  • Provide the path to IPA (from this folder), path to Wildcard.mobileprovision file and choose your development signing identity.
  • Specifying of the custom entitlement is not recommended. Leave that box empty. Entitlements are taken from your provisioning profile automatically.
  • Changing the bundle ID is optional. Rather leave that box unchecked.
  • Resign the IPA and deploy it via iTunes or Xcode/Devices.
  • If it didn’t work, make sure that the device was included in your provisioning profile.

 

 

Preparing the Debugging Environment

Debugging on iOS (both device and Simulator) is performed with Safari WebInspector on the Mac. We recommend using the latest version of Safari or at least the version corresponding to Safari version on your device.

To enable the remote debugging you will need to enable the WebInspector in Safari settings (option “Advanced”).

JSBridge Guide 010

Then enable the “Develop” menu in Safari preferences (Advanced tab) on the Mac.

JSBridge Guide 011

Now you can connect your iPad via USB, open the client application and navigate to the form showing your offline HTML. After that you should observe the submenu “iPad” under Safari Develop menu showing the client application and an HTML file opened on its form.

JSBridge Guide 012

Click on it to connect WebInspector.

JSBridge Guide 012

HINT: If Safari misses the iPad option, try to quit it and restart once more.

 

 

Changing the local copy of the Offline HTML files

Typical development cycle for Offline HTML development is as follows:

  • Changing the local copy of the Offline HTML solution
  • Uploading changed files to Woodford
  • Publishing Woodford mobile project
  • Synchronizing client app to get new customization
  • Verifying the changes

We consider it to be very frustrating and time consuming, but fortunately, there are some shortcuts that can save a vital portion of the development/testing time.

One of the shortcuts is the possibility of changing the local copy of the Offline HTML files, which is possible on iOS Simulator and Windows platforms. Offline HTML resides in the WWW subfolder of the app data.

In case of Windows 7 Desktop application, the application data are stored in the following folder:

%APPDATA%\MobileCRM

which is usually:

C:\Users\{UserName}\AppData\Roaming\MobileCRM

 
In case of Windows Store (Win10), it’s:

%LOCALAPPDATA%\Packages\Resco.MobileCRM_{StoreId}\LocalState\MobileCRM

which is usually:

C:\Users\{UserName}\AppData\Local\Packages\ Resco.MobileCRM_{StoreId}\LocalState\MobileCRM

 
In case of iOS Simulator, app files are also mounted to the file system at:

~/Library/Developer/CoreSimulator/Devices/{SimulatorID}/data/Containers/Data/Application

To find the right application within this folder, try to search for specific file (like JSBridge.js) inside this root.

To find out the SimulatorId of the currently booted simulator, open Terminal app and insert the following command:

xcrun simctl list | grep Booted

 

 

Overriding the Offline HTML root

Another shortcut that can save a lot of development time is overriding the Offline HTML root. Once you installed the JSDev edition of our app from Debug Builds (see above), the app handles special URL that can change the root from which it takes the Offline HTML files.

First, place your Offline HTML solution into local directory or remote HTTP server reachable from testing device. It can be any type of the server (IIS, Apache,…), but we recommend using Visual Studio Typescript project which automatically starts the IIS Express instance with your Offline HTML files. We provide a template for empty Offline HTML solution here:

https://www.resco.net/downloads/OfflineHtml.zip

HINT: To make the IIS Express available from LAN, you need t specify your IP address as target URL instead of “localhost” and use the following command to expose the URL:

netsh http add urlacl url=http://10.211.55.5:4199/ user=everyone

When the local copy of the Offline HTML solution is available via HTTP, follow these steps to override the root:

  • Go back to your iOS/Android device or simulator
  • Open Mobile CRM JSDev edition app and minimize it – let it run in background
  • Open the web browser (Safari/Chrome) and type in the following URL:
    mobilecrm:// htmlRoot?set=http://{IP Address}:{Port}
  • Mobile CRM app should maximize from background and it will display a toast message notifying that the Offline HTML root was changed
  • After this moment, the app should take all Offline HTML files from your HTTP server instead of local app storage

If you prefer the command line tools instead of typing the URL to browser, use the following ADB command for Android:

adb shell am start -a android.intent.action.VIEW -d mobilecrm://htmlRoot?set=http://{IP Address}:{Port}

or this one for iOS Simulator:

xcrun simctl openurl booted mobilecrm://htmlRoot?set=http://{IP Address}:{Port}

 

 

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 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 and 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 the 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 will be queried – in this case Accounts. Then by adding attributes we specify that only Account’s Name and City will be 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:

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):

JSBridge Guide 014

 

 

DynamicEntity

Another way to work with the 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 then plain array – just specify ‘DynamicEntities’ as the first parameter in the FetchXML execute method.

On the following examples, we will 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 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 JSBrdige. In the next example we will demonstrate how to use all above-mentioned methods and properties, and also how to create a reference (lookup) for the parent entity.

Code:

var newContactId = null;
function DynamicEntityExample()
{
   // 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 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!
 

 

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 Manger 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 the data access, Resco JavaScript Bridge enables also an access to the standard UI elements of Resco MobileCRM application like forms and lists. 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 like entity, form, controllers etc. so 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 it will be MobileCRM.UI.EntityForm.requestObject.

In the first example we will 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 want to indicate that save is necessary (the form is ‘dirty’). If the property is set to false, the user is able to close the form so the information entered on the HTML tab would be 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 have ensured the user will see the Save/Discard Changes/Cancel dialog. Then we can do actual saving of the data in the onSave callback (see next chapter).

 

 

OnChange

In the second example we will show you 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 … – you name it. 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, but 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 and 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 adding this page on the form as a tab in Woodford, make sure that Delay Load option is turned off. 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 will use fetch, but the result will be an array of dynamic entities we can work with. As a matter of fact, it will be only one dynamic entity, because we will 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 will update the email of the selected customer with 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. Similarly 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.

JSBridge Guide 015

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 will 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 first step we check if 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 reg. expression we return true, to apply all changes and close form.
 

 

Form Commands

So far, we have examined how to access the form and react to the change on an item, but now let’s take a look at another aspect of the form – the commands (actions). In this section we will show you how to access a command from JavaScript, but also how to create a command in Woodford, while this is a necessary part of the process. In the example in next section you will find a way 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.

 

 

How to create a command in Woodford:

The first step of binding a command is creating the actual command by using Woodford’s form designer. Open the form where you want the command to be displayed and hit Edit Commands. There, select ‘New Command’ and enter the name and a label for your custom command.

Please note that a command defined like this, will be visible on all detail tabs (tabs like ‘General’, ‘Address’, etc…), but won’t be available on associated tabs, IFrame tabs, Map tabs, etc…

JSBridge Guide 016

Once your command is defined, just add the HTML/JavaScript file in the Offline HTML section in Woodford and by using Add IFrame and Browse add it onto the form.

JSBridge Guide 017

In our scenario we will create a command that will delete the associated parent customer for a contact entity as well as the contact itself. So we have added a custom new command on 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 command, we specify that we want this callback to be called only for our command with the name ‘custom_Delete’. 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 to one parent only, but in 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 call to 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 will 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 will be displayed ONLY on the IFrame tab!

As for the commands themselves, they will show different special features of JavaScript Bridge – you will see how you can 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 the whole UI isn’t created in HTML but going back and forth from the HTML to standard UI is necessary. There are three Form Manager functions available for this – one for opening new entity dialog (edit form for a new entity), another for entity detail dialog (contact information form) and third one for entity edit dialog.

In the following example we will create a simple HTML page for a Contact associated to an Account. It will display just the Contact’s name and after clicking on one of items (name), standard Contact edit dialog will appear. There is one more option to create new associated contact: by opening entity new dialog with a predefined Account id. In our solution we don’t use auto reload when you save new created associated contact. We just create the button with a function to reload the page. We also use some basic Javascript and DOM html functions to print result on page.

function OpenForm()
{
   data.innerHTML = "";
   var entity = new MobileCRM.FetchXml.Entity("account");
   //entity.addAttributes();
   var linkEntity = entity.addLink("contact", "parentcustomerid", "accountid", "inner");
   linkEntity.addAttribute("contactid");
   linkEntity.addAttribute("fullname");

   entity.filter = new MobileCRM.FetchXml.Filter();
   entity.filter.where("accountid", "eq", accountId);

   var fetch = new MobileCRM.FetchXml.Fetch(entity);
   fetch.execute(
      "Array",
      function (res) {
         if (res && res.length > 0) {
         for (var i in res) {
            var contact = res[i];
         try {
            var a = document.createElement("a");
            a.href = "#";
            a.id = contact[0];
            a.addEventListener("click",
               function (e)
               {
                  MobileCRM.UI.FormManager.showEditDialog("contact", e.target.id);
                  reloadData();
               },
            false);

            a.appendChild(document.createTextNode(contact[1]));
            data.appendChild(a);
            data.appendChild(document.createElement("br"));
            }
         catch (err)
         {
            alert("Exception Error : \n\n" + err);
         }
      }
   }
   },function (err) {alert(err); });
}

   function addAssociatedContact()
   {
   // Create reference for associated account
   try{
      var target = new MobileCRM.Reference("account", accountId);
      var relationShip = new MobileCRM.Relationship("parentcustomerid", target, null, null);
      MobileCRM.UI.FormManager.showNewDialog("contact", relationShip);

      // Show all associated contact at the begining;
      reloadData();
      } catch (err)
      {
         alert("Exception : " + err);
      }
   }

   function reloadPage()
   {
      document.location.reload(true);
   }

   function reloadData()
   {
      OpenForm();
   }

At first, we use FetchXML to get data and in loop of getting result data we create the DOM element object of “a” and create event handler “onclick” with a function to Open Edit Form for specified Account. Function “reloadData” is used to fetch and display data.

a.addEventListener("click",function (e) {
   MobileCRM.UI.FormManager.showEditDialog("contact", e.target.id);
   reloadData();
   }, false);

To create a new associated contact we use Method for FormManager object, showNewDialog.

We need to create a reference for the specified account and create a relationship object, which contains our reference as a target of relationship.

function addAssociatedContact()
   {
      // Create reference for associated account
      try{
         var target = new MobileCRM.Reference("account", accountId);
         var relationShip = new MobileCRM.Relationship("parentcustomerid", target, null, null);
         MobileCRM.UI.FormManager.showNewDialog("contact", relationShip);

         // Show all associated contact at the begining;
         reloadData();
      } catch (err)
      {
         alert("Exception : " + err);
      }
   }

Our Html looks like this:

<h3>Account : </h3>
   Name <p id="accountName"></p>
   <button id="reload" onclick="reloadPage()">Reload page</button><br /><br />
   <button id="reload" onclick="reloadData()">Reload Data</button><br /><br />
   <button id="createNew" onclick="addAssociatedContact()">New Contact</button><br /><br />
   <div id="data">
</div>

 

Metadata:
Metadata is by definition ‘data about data’ and MobileCRM.Metadata class allows access to the entity metadata information. There you can find information like entity permissions, types of entity properties, precisions, formats and more. In some situations – like when you are displaying the data on a custom HTML page and would like to check if the entered data complies with those conditions – this might come really handy. In the following example we will show you how to get field’s maximum and minimum value and also – for a lookup or parent customer field – to get the lookup targets.

We are using Metadata’s function “requestObject” in the code, to get the managed metadata object. The mechanism is similar to other requestObject calls in Resco JavaScript Bridge, but in the metadata case the object containing the metadata is then (once the success callback is called) accessible also directly via MobileCRM.Metadata functions.

To access entity’s metadata from the callback you can call getEntity method and pass entity’s logical name (all lowercase) as a parameter.

var metaEntity = MobileCRM.Metadata.getEntity("account");

The following example showcases a typical use of Metadata class in a custom HTML/JS scenario. Metadata for Order entity is requested and for a couple of fields – like discount percentage, total amount, name and a custom email field – meta properties are accessed.

These metaproperties store information about maximal length (name, email) or minimal and maximal value, which are checked when the corresponding input elements on the HTML page have changed. If the input doesn’t correspond with the metadata, an error message is displayed.

Example:

Javascript :

var checkId;
function MetaDataCheck(id)
{
   checkId = id;
   MobileCRM.Metadata.requestObject(onMetadataLoaded, function (err) { alert(err); }, null);
}

function onMetadataLoaded()
{
var metaEntity = new MobileCRM.Metadata.getEntity("salesorder");
var discountPercentageProperty = metaEntity.getProperty("discountpercentage");
var emailProperty = metaEntity.getProperty("new_customeremailaddress");
var nameProperty = metaEntity.getProperty("name");
var totalamountProperty = metaEntity.getProperty("totalamount");
   switch (checkId)
   {
      case "name1":
      if((nameProperty.maximum && nameProperty.maximum !== undefined))
      {
         if (name1.value.toString().length > nameProperty.maximum) {
            alert("name contains more character > " + nameProperty.maximum);
            name1.style.border = "thick solid #FF0000"
         }
         else
            name1.style.border = "";
      }
      break;

      case "email":
      if ((emailProperty.maximum && emailProperty.maximum !== undefined))
      {
         if (email.value.length > emailProperty.maximum) {
            alert("Email contains more characters > " + emailProperty.maximum);
            email.style.border = "thick solid #FF0000";
         }
         else
            email.style.border = "";
      }
      break;

      case "percentage":
      if ((discountPercentageProperty.maximum && discountPercentageProperty.maximum !== undefined))
      {
         if (percentage.value > discountPercentageProperty.maximum) {
            alert("Percentage > " + discountPercentageProperty.maximum);
            percentage.style.border = "thick solid #FF0000";
         }
         else
            percentage.style.border = "";
      }
      break;

      case "totalamount":
      if((!totalamountProperty.maximum || totalamountProperty.maximum === undefined) || (!totalamountProperty.minimum || totalamountProperty.minimum === undefined) )
      {
         totalamountProperty.maximum = 300; // set some value if not defined or exist
         totalamountProperty.minimum = 0;
      }
      if (totalAmount.value > totalamountProperty.maximum || totalAmount.value < totalamountProperty.minimum)
         {
            alert("Input Total Amount between " + totalamountProperty.minimum + " AND "+ totalamountProperty.maximum );
            totalAmount.style.border = "thick solid #FF0000";
         }
         else
            totalAmount.style.border = "";
         break;
   }
}

HTML:

<h2>Order Detail fields</h2>
<div id="data">
   Name : <input id="name1" type="text" onchange="MetaDataCheck(this.id)" /> <br />
   Email Account : <input id="email" type="email" onchange="MetaDataCheck(this.id)" /><br />
   Discount Percentage : <input id="percentage" type="number" onchange="MetaDataCheck(this.id)" /><br />
   Totalamount : <input id="totalAmount" type="number" onchange="MetaDataCheck(this.id)" /><br />
</div>

JSBridge Guide 018

 

 

EntityList

 

 

References to objects and methods that we work with

 

 

Create a view with IFrame for EntityList in Woodford

In the beginning of working with entity list object, we have to create and bind iFrame to an existing or a new view. In our example, we create such type of public view on account entity.

JSBridge Guide 019

If we have an empty view for the desired entity, we can bind iFrame, that contains logic.

JSBridge Guide 020

The dialog, that binds iFframe can be set up. Here is a short explanation of ‘Provides data source’ checkbox. We will use those settings in a specific scenario later in the document.

Checked ‘provides data source’ means that you will retrieve fetch with entity result that will be displayed in an entity list.

Unchecked ‘provides data source’ means that it will be impossible to override the data source with the setDataSource method and you won’t be able to change it with fetch.

In both cases, we attach the script properly if url address is correct. It is good to know that all methods will work except those, which set data source.

 

 

Sample scenario shows how to use EntityList object and its methods

In the next part, we will discuss and explain scenarios which can help you understand logic and use of mentioned objects. Since we know how to add iFrame to view, let’s consider that we have one script bound on Account entity view.

 

 

Retrieve fetch data source and create view

Our first sample scenario will load data by using the fetch method and set an additional filter for the retrieved data. In this sample, we use only basic functions which every fetch contains. 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 first example, we can see that we can modify the fetch source by adding filter or basic fetch methods. All these inputs create array result, what represents our source. Each element data in array are displayed in single row. For a better explanation, let’s see what it looks like in the app.

JSBridge Guide 021

From the screenshot it is obvious that the correct fields values are displayed. In the code from reference page, we want the fetch output as an array of dynamic entities. This object carries values of the entity property.

IMPORTANT: It is very important to make sure, that all the properties which we filter or display, must be enabled and we display only those which are bound on the row.

 

 

Commands

Entity view contains entity list what is represented by rows, each row can contain some Rows command (buttons). Next command which we call primary command is part of view, as well the multi command. Each command can be created in Woodford, except of 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 list contains this button. In our scenario, we create custom command, which creates a photo attachment annotation for the currently selected entity.

Woodford setup:

JSBridge Guide 022

Now we can insert the logic into iFrame that will create the annotation. This operation requires the user to have a permission to take a picture and code to use DocumentService object and access to files.

Most important part of the code and this section is to register handler which listens for the specific command name. To run logic when the command is executed, we need to use callback. In this callback, we retrieve entity list object with all properties and methods available. In situation like this, we need to access the selected record from 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, 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 are able to 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 exact name.
In the next example we will take entity list context property of current selected record and will display a message box with those properties. Once user selects the option, we can make a call with 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 like:

JSBridge Guide 023

 

  • Primary Command – This type of command overrides primary entity list view command. It can be created only in the background of the code, from script. Its handler will be executed after this button was clicked.

    Screenshot from the app:

    JSBridge Guide 024

    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);

  • MultiSelect command – Command allows user to select more than one record. Entity list object contains a method which handles these commands, it is the same as first example scenario. We need to use onCommand method with a valid command name.
    In our scenario, we iterate through the selected entities and compare them with the predefined entities’ values. It is a very simple validation for multi-selection on entity list.

    Woodford setup:

    JSBridge Guide 025

    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 entity list. This methods and functions allow user 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

Entity list object contains events which can be explicitly called and handled by specific methods. Native Entity list allows user to edit fields, select them, call buttons etc. In this section we will 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.

Important: We need to set kind of field as editable. See point 3 in woodford setup.

Woodford setup:

JSBridge Guide 026

Now we must register onChange event handler to allow us control what value user writes to fax field. In our case we just check if the value is not empty and if it is – we explicitly set the 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 in case if you know what row and property you need directly to edit or edit and save immediately, then check out this 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 the similar scenario as in previous example, except of the onSave event handler and set ‘errorMessage’ property in case of validation fail. See this example and the code snippet on reference page for more. The onSave handler works the same on entity form and another part of 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 click event and its context. Imagine that we want to handle clicking on 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.

If you face any issues, just let us know, we will try to help you as much as we can.

 

 

Global Events

 

 

References and objects what we use in this document as prerequisites

Prerequisites objects are dependent on each other. They work as simple event listeners and handlers similar to other programing 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 will represent the name of the event you will raise or listen on. 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 on, you have to bind it. To unregister event, just set bind property to false.

 

 

Application’s pre–defined events

MobileCRM application contains many events, some of which can be handled by your custom html, javascript code. To listen on those events, you need to register event listener with the specific event name. Here are the available pre-defined events of MobileCRM application:

  • EntityFormClosed – This event will be fired 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 Iframe bound with a listener on this method. ……….. After this event is registered, we can rely on the handler callback being fired when any opened entityForm will be 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 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 scenario that suits this event the most.

    Sample Scenario: Since this event is firing during various situations, most of time we want to handle it from the application start. In this special case, we will have to register this event during an application opening. The question is: How to bind iFrame with handler to homeForm? Answer: We have only one option, to place the html file to the first position of home items. If this happens, the Application will replace the native UI with your custom html.

    JSBridge Guide 027

    Note: You have to name this iFrame only as UIReplacement or HomeReplacement. Other name of Iframe will be ignored and their content, including the script part will be ignored until you will 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. In this moment we will 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 home form.

    Object: https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_HomeForm

    Back to our example, we will 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 will be displayed. Of course, this is just one of many examples for how to use this pre-defined event. This just shows how to control the state of an application, when synchronization starts. This handler is not restricted for use only on home form. You can feel free to use it on another iFrame bound in available places like entity form, iFrameForm or entity list.

    SyncFinished – Pre-defined event that works similarly to previous SyncStarted, except of the state when it is fired. We can rely on this event, when synchronization has finished.

    Event handler callback carries object as 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 previous handler, we are able to listen on this event anywhere from the bound iFrame’s possible placing.

    Sample scenario: Imagine that we will create record in mobile crm application, 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 saving event of this record with callback, which will invoke synchronization from our script, since we have listener for 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. I am speaking about case, when your app has been synced and downloaded new customization. In this case a modal window will appear ‘Application was updated’. This scenario will not fire global event syncFinished, because this is a special event on home form. Please, check out the short code snippet that shows how to write this event. Implementation of this code is the same like 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 are the parameters we send. The name of event is our custom event name that we fire and listen on. Handlers contain the logic that we need to implement, when event fires or was fired. Arguments that the handlers carry, can be a type of object, it could mean whatever, since you create and handle them by your logic.

     

     

    Unregister the events

    In the case, when we don’t want to listen on this event, we have to call the same method, but parameter bind must be set to false. This scenario can be described like += and -= in other programing languages.

     

     

    LookUp

     

     

    References and Objects that are used for look up creation

    All 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 view template for Lookup in Woodford

    You can create template that will be displayed in your lookup. You have an option to create template in Woodford with basic or advanced templates. In our environment we will use this template and view as DEFAULT for each lookup.

    In Woodford:

    JSBridge Guide 028

     

     

    Create filter for view’s records with the use of Row Script

    In some situations, you need to create simple or less simple filters that define which records you want to see. In our case we want to display the account data that has the same address1_city as the associated contact.

    In Woodford:

    JSBridge Guide 029

    Note that if you want to see the field on view, you need to bind it to the row. For further details about filters and views, please check out the following links:

    https://www.resco.net/woodford-user-guide/#__RefHeading__5823_1627906509

    https://www.resco.net/woodford-user-guide/#__RefHeading__5827_1627906509

    https://www.resco.net/woodford-user-guide/#__RefHeading__5829_1627906509

    https://www.resco.net/woodford-user-guide/#_Toc483815907

     

     

    Bind iFrame on Entity Form/Home Form

    Please check out this short guide and a section OfflineHTML.

    https://www.resco.net/wp-content/uploads/2016/12/JSBridge-Guide.pdf

     

     

    Independent LookUp

    This kind of lookup can be used from forms/home/view/ html files. It doesn’t require the use of detail view object, so you can create lookup/multilookup from html file and set what view you want to see and the best part is adding fetchXML filter to view.

    Each lookup object mentioned in the first section contains a method to add fetch xml filter. Please check this reference to find out more about what method we talk about. Name of the method is addEntityFilter. Please keep in mind that this fetch filter will just add another filter that is described by fetch.

    https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_MultiLookupForm_addEntityFilter

    Another good thing that we should know, is how to define views between which we can choose.

    JSBridge Guide 030

    • Normal LookUp form – This is the normal lookup form that displays one view form that we use to select one unique value for Lookup.

      The code from reference page: https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_LookupForm_show

      JSBridge Guide 031

    • MultiLookUp form – The difference between this one and a normal lookup is that we can use use multiselection.
      We receive the selected items in success callback. After the tap on back button, the callback is executed in case all operations were successfully done, we get callback to success, otherwise to failure.

      Custom M:N relationship MultiLookup form:
      If user has entities, that could represent M:N relationship, this lookup is the best way to achieve this. The behavior of lookups can be almost fully developed by JSBridge.
      Imagine that we start from one entity (Appointment) and we want to create the display in this lookup on the right side that doesn’t have any reference to appointment. 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 out the correct data at the right side of the lookup. We are able to add an entity filter that will use this appointment’s values in fetch. For example, use starts on field in 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 M:N and this entity doesn’t know anything about appointment, 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 reference to appointment. So the best way is to set the data source by using Array of references of third type entity object, which refer 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

      JSBridge Guide 032

    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 object, we can expect that it will need the detail view and a detail item. This lookup is bound on lookup field item. For example, we are on contact entity form and the first element is account lookup field. By using this object, we are able to create a custom filter and a view that will be shown when user taps on this item. We have two types of lookup that we can bind on this item. First one behaves like inline, another displays lookup form, as mentioned before.

    • Inline Lookup – This lookup will look as follows and it will use methods like normal lookup.

      Example of code using a custom filter based on 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 – Open normal lookup form.

      Example of the code:

      /// Create dialog setup what behaves like LookUp form
      dialogSetup.addView("account", "Default", true);

    As was mentioned before, we have to get the detail view and bind these Items. For this we use EntityForm request object. Last phase is placing the items to specific positions. Please check out this link to see what possibilities you have and which parameters to set if you use just dialog/inline lookup.

    Example of the code:

    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

     

     

    How To add iFrame on questionnaire form

    First of all, we need to log in to management console with valid credentials.

    JSBridge Guide 033

    Now we can select project we want to design and import html files into it. Now select entities.

    JSBridge Guide 034

    Find Questionnaire entity and click on it.

    JSBridge Guide 035

    Now select “Manage Data” action and select a new or an existing questionnaire.

    JSBridge Guide 036

    IMPORTANT: Now you have to use the option: SHOW SYSTEM FILES!

    Please check the navigation bar and the option you have to enable.

    JSBridge Guide 037

    If you scroll down, you can find resco_script text field. The value you set for this field will be used as the name of the html file we will search for and bind to the current questionnaire form.

    JSBridge Guide 038

    IMPORTANT: The file name must consist of the relative path that’s using the application folder.

    In our case, the name is test_questionnaire.html.

    • file:// represents the application www folder, where the offline html files are stored.
    • Next parts of the url are the folder and html file.
    • This file must be in saved in the application www folder, where the offline html files are stored.

    In the project html section, it could look like this.

    JSBridge Guide 039

     

     

    How to write scripts for the questionnaire by using JSBridge api

    The questionnaire form contains properties and methods to access/manipulate with question groups , direct question. The basic handlers like onSave, onChange are implemented as well.

    In the following part of the document, we will show you some basic operations. The object what we use can be found on this reference page.

    References:

    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. At First, we will discuss the basic handlers like onSave, onChange and requestObject (e.i. onLoad).

    1. First case will show us how to access the data on questionnaire form and set property of group and question.

      Code:

      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 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 case when the regarding entity must refer to parent.

      Next part contains the method to access group and question directly by its name or the id.

      The 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 visibility of the group as well. We can set the properties also on the question if its available. We changed the description. In the application it looks like this.

      JSBridge Guide 040

    2. Next, we will show how to work with handlers. We will show simple onChange handler that will set the error message and validation on specific question.

      Code:

      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 line, since it shows how to access the changed item. Every time some question changes, this handler will be fired, so 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 will be displayed after user tries to close the form.

      JSBridge Guide 041

    3. Next part shows how to handle onSave event and cancel validation and discard the error message if the value of 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);

     

     

    UI (Home) Replacement

     

     

    References and Objects that we use

     

     

    Set up IFrame in Woodford

    In Woodford you are able to place IFrame to home form. That depends on the name of file that will replace whole UI or just Home Form. In the next screenshot you can see how to bind it and how the difference looks like in the application.

    Warning: The most important part is placing the html file as the first item in home section.

    JSBridge Guide 042

    1. Replace the UI of the whole app

      In this case, your html file will replace the whole UI. This allows you to make the start-up application screen to have looks according to your requirements.

      To achieve that, add IFrame with the file name ‘UIReplacement.html’ in Woodford into home section, see the setup above.

      JSBridge Guide 043

      As you can see from the screenshots, this tap bar contains a back button that closes the application in this case. On the other hand, UIReplacement gives you full control of UI.

    2.  

    3. Replace the Home form’s content

      This allows user to set home form content with IFrame, the setup can be done in Woodford as well.

      To do that, add IFrame in Home section with the file name ‘HomeReplacement.html’, it will replace only home form not the whole UI. It means that the native UI application top bar stays default.

      JSBridge Guide 044

     

     

    Simple use cases and code snippets

    Here we will discuss how to work with home form and its belonging methods, to achieve simple scenario in the app.

    • Load home items – Many times when we want to replace UI or home form, we need to access the items on native form. It is necessary if we want to display them or set the visibility for the specific one by desired condition.

      Code snippet:

      function loadHomeItems() {
         MobileCRM.UI.HomeForm.requestObject(function (homeForm) {
            /// <param name="homeForm" type="MobileCRM.UI.HomeForm"/>
            for (var i in homeForm.items) {
               if (homeForm.items[i].name == "account") {
                  homeForm.items[i].isVisible = false;
                  break;
               }
            }
            /// Here you can call method to display items
            /// You can create e.g. HTML DOM elements with information about home item.
         }, MobileCRM.bridge.alert, null);
      }

      From the code snippet, it is obvious that we need to request HomeForm object to access its methods and attributes. If the object is requested, you can work with home items. For example, you can display them like HTML DOM element with the title of an item name.

    • Open Home item – Next option is to use code to open home item by using the correct name.

      function openHomeItem(name) {
         ///<summary>Open desired home item.</summary>
         ///<param name='name' type='string'>The home item name</param>
         MobileCRM.UI.HomeForm.openHomeItemAsync(
         name,
         MobileCRM.bridge.alert,
         null);
      }

      This code opens the list of accounts or contacts, dashboard or setup, basically all items which are available on home form.
      Some of the items have special names, to find them out, please visit this reference and check the attribute name of the method.

      https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_HomeForm_openHomeItemAsync

      In scenario, we want to open an item that is nested in the group of items. Then it’s required to pass the location to the item. From the main group over the subgroup/s till we reach the item.

      In the next reference, you can find a way to open the nested iFrame.

      https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_HomeForm_openHomeGroupItemAsync

    • Close Home item – This is the opposite action to open. Here you can find the reference link to the method.

      https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_HomeForm_closeHomeItemAsync

    • Update home item – This method sets the attributes of the home item, like Title, badge, etc. I did simple case, which runs an aggregate fetch to retrieve the number of existing accounts and sets the badge with this value. In my scenario, I run this method while the page is loading. The important thing is to know that it updates the native home item, so it requires the display of a native form to see the changes. For that I use the method to hide home replacement.

      Code snippet:

      function updateAccountAttributes() {
         ///<summary>Fetch accounts count using aggregate function, to update home item.</summary>
         var entity = new MobileCRM.FetchXml.Entity("account");
         var attr = entity.addAttribute("statuscode");
         attr.aggregate = "count";
         attr.groupby = true;
         var fetch = new MobileCRM.FetchXml.Fetch(entity);
         fetch.aggregate = true;
         fetch.execute("Array", function (res) {
            MobileCRM.UI.HomeForm.updateHomeItems([{
               path: "account",
               title: "Accounts",
               subTitle: "Synced Accounts",
               badge: res[0][0]
            }]);
         }, MobileCRM.bridge.alert);
      }

    This method updates the attributes, but I will not be able to see it until the native home form is not visible.
    The code reference to hide UIReplacement – https://www.resco.net/javascript-bridge-reference/#MobileCRM_UI_HomeForm_hideUIReplacement

    This is what it looks like on the native Form:

    JSBridge Guide 045

     

     

    Access the synchronization result object when sync finished

    Some cases require an access to synchronization result after this event has finished. Here is an example for how to display the sync progress and handle the end of synchronization.

    If we speak about the synchronization event, we mean the global event handler. This event must be registered before it is finished.

    In the beginning, we run the synchronization from the command (button) in our html file. We can choose between the background and the foreground sync. Note that the background synchronization can run only if we saved password and set that we don’t need to display the SyncLogin form.

    function sync(background) {
       /// <summary>Display Synchronization progress on UIReplacement.</summary>
       /// <param name='background' type='Boolean'>If <c>True</c> app will run background synchronization if it is possible.</param>
       if (background) {
          MobileCRM.Configuration.requestObject(function (config) {
             ///<param name='config' type='MobileCRM.Configuration' />
          if (config.settings.savePassword && !config.settings.requireSyncLogin)
             MobileCRM.Application.synchronize(true);
          else
             MobileCRM.Application.synchronizeOnForeground(false);

          setTimeout(onSyncProgress, 1000);
          }, MobileCRM.bridge.alert, null);
       }
       else
          MobileCRM.Application.synchronizeOnForeground(false);

       setTimeout(onSyncProgress, 1000);
    }

    In the code above, we try to run synchronization, in case that we are not allowed to run background sync, we display the synchronization form. An important method is onSyncProgress, it is displayed by using the delay of the synchronization result text. This is the way to see what happens during the synchronization process.

    The code snippet:

    function onSyncProgress() {
       /// <summary>Display Synchronization progress on UIReplacement.</summary>
       MobileCRM.UI.HomeForm.requestObject(function (homeForm) {
          var progress = homeForm.syncProgress;
          if (progress) {
             var labels = progress.labels;
                var syncProcessText = progress.percent + "% " + labels[0] + "\n" + labels[1] || "";
             setTimeout(onSyncProgress, 1000);
          }
       });
    }

    • Global events – When you want to handle the global app events in javascript. (See global events in the JSBridge Reference https://www.resco.net/support/jsbridge-guide/#Global_events).
    • Hide Replacement – In many scenarios, users want to implement logic to handle app launch or registering events on home form, however they want to keep the Native UI. For this case, you can call hideUIReplecament() method on home form during onload event handler.

    Last part of synchronization is an event that is fired after the synchronization has finished. It allows handling of the sync errors, displaying the app login in specific scenarios, etc. Next code is a simple handler which checks whether customization is ready or not, and checks for the connection error during synchronization. If connection failed, we display the app login form.

    The code snippet:

    function onSyncFinished() {
       /// <summary>Handle event on home form, when syncronization has finished.</summary>
       /// <remarks>This metho must be registered during onload, it works like normal Event handler.</remarks>
       MobileCRM.UI.HomeForm.onSyncFinished(
          function (homeForm) {
             /// <param name="homeForm" type="MobileCRM.UI.HomeForm"/>
             if (homeForm.lastSyncResult.newCustomizationReady && !homeForm.lastSyncResult.customizationDownloaded)
                MobileCRM.bridge.alert("Application update is available");
             else if (homeForm.lastSyncResult.connectFailed)
                showAppLoginForm();
             else
                return;
          }, null);
       }

    Note: This handler must be registered before the event ends. For example, you can register during onload or when you run synchronization.

    In the previous method we used lastSyncResult to check if connection failed. This object contains also an attribute to check whether an application was locked or not. Based on the value, it is possible to display the application login form during loading of this replacement iframe.

    • Can use password’ – One of the most important setting properties, that needs to be used in many scenarios, especially, when you want to access the data, is canUsePassword setting. It is used to indicate whether user is authenticated and is allowed to do appropriate operations.

      Example: The user wants to set the badge with activated or inactivated accounts by using fetch, like in the previous example. In this case, we are not sure whether data access is provided to the user, if the records will be fetched. In situation, when this fetch is quick enough, we will get 0 results or server response messages. To prevent this situation, we can use this setting property and wait until the value is true. It indicates that the user has the necessary privileges and is authenticated to access data or server information that require full authentication.

    • Handle Launch’ – Some scenarios require handling the launch of an application. For example, you need to pass parameters to MobileCRM application and handle it in your HTML during application launch event.

      For this scenario we support opening of our application with the use of valid url scheme and setting the parameters as well.

      URL scheme: mobilecrm://open?jsbridge;parameter
      How to handle: If you define a function handleLaunch(), this method will be called when the Resco app is launched.
      Example: Url scheme (mobilecrm://open?jsbridge;data) was opened from email message. Register method that is placed in Home form, in html, see Setup IFrame in Woodford section, in the top of the chapter.

      function handleLaunch(res) {
         MobileCRM.bridge.alert("onHandledLaunched: " + res);
      }

      Result of handler:
      JSBridge Guide 046

       

       

      TypeScript

      This section is dedicated to development with the use of TypeScript language. Currently we are preparing our JSBridge api solution to fully supports development using this language.

      Currently we provide TypeScript definition file, it can be found in the OfflineHtml zip solution.

      For more information, please follow this link to section ‘Overriding the Offline HTML root’

      Reference: https://www.resco.net/debugging-offline-html-solution/

       

      If you have any question or need the whole sample code, just let us know and contact us on mailto:mobilecrm.recso.net