Resolver

The Resolver handles data that needs to be fetched from the server and displayed on the front end. Each Resolver method runs an asynchronous server request and returns a promise. Any promise can have a then function appended to it that will run once the promise is resolved, either successfully or on failure. Any FEF life cycle event functions that return a promise via the resolver will halt the execution of the FEF app until the promise has been resolved. This is a key concept to understand in bootstrapping your FEF application with the correct data before the application renders.

Query

Query Infor Nexus' Platform using the OQL query language. If your FEF is a dapi app, you will be querying over the RESTful API. More information is available here.

All query results are meant to be READ-ONLY by default. If you wish to allow editing, deletion, appending, or insertion to your query result set - add session-managed=true to your ResolverOptions parameter as noted below.

function query(resource, queryOptions, resolverOptions)

This method returns a Promise - thus, you can utilize the .then( function(result){}) method to perform an action once the promise has been resolved.

Resource The global identifier of the object you are updating.
QueryOptions An object that can contain the following fields:
  • params — Holds the OQL statement in the oql field
  • slotName — Identifier of the queryResults in the DataRegistry
  • alias — Same function as slotName
If slotName and alias are both missing, the data can be accessible through using the resource name as its reference.
ResolverOptions An object that can contain the following fields:
  • sessionManaged — Determines whether the DataRegistry will keep track of the data returned, i.e. if the query results are changed, will the data be marked dirty to prompt the end-user to save. Adding this option to your query will automatically make your app a multi-root app: because FEF will track the options that are returned from the query. Add the corresponding data types to your app's SaveStrategy and FEF will automatically perform the changes to the data on the server; i.e., persisting a change to the data
  • bufferingOptions — An object that can contain the following fields: startIndex and fetchSize. This can offset or limit the number of results, respectively.
  • resolveOnServer — boolean, true/false value. If your query already has run and references data in the DataRegistry, it will not resolve again on the server. Set this option to true to send your query to the server no matter what
If slotName and alias are both missing, the data can be accessible through using the resource name as its reference.
Example — Search for Specific OrderDetails

Query the Infor Nexus platform for any orders available to my org that contain the word 'foobar' in the poNumber field and limit the results to 10 orders.

Facade.Behaviors.App.onLoad( function(behaviorFn,args){
    //oql statement
    var oqlStatement = "poNumber contains 'foobar'";
    return Facade.Resolver.query("OrderDetail", {
        params: {oql: oqlStatement},
        slotName: "queryResults"
    }, {
        sessionManaged: true ,
        bufferingOptions: { fetchSize: 10 }
    });
});

To iterate over query results, use your DataList's forEach function and the promise returned by the query's then function. For more information, see the DataList Prototype Section

Facade.Resolver.query(...).then(
    function(resultSet){
        resultSet.forEach(function(listItem, index){
            //act upon query result set
        });
    });
Query Builder

FEF has another construct that can similarly send queries to the server, that being the Query Builder. The Query Builder is a shorthand way to write reusable queries as it offers slightly different syntax but produces the same promise as a Resolver.query would.

For queries that are run multiple times in your FEF application, it is best practice to user the Query Builder. For example, if you ran queries based on user input which formed the OQL; query builder provides a simplistic construct to run the query over and over again with varied parameters.

The following functions can be used to build your query

  • param(name,value) — add a param to your query. If your app is a DAPI app, add your oql query here using the name oql and set the value to your oql query like done below
  • registerAs(key) — registers the returned datalist in this position in your DataRegistry
  • type(value) — registers the server result set as of type value; this is helpful when lazy-loading queries, this property will alert associated components what type to expect once the query request resolves
  • bufferingStrategy(strategy) — set a buffering strategy for the query (more on this below)
  • execute(resolverOptions) — send your query to the server to retrieve a result set. Pass in { resolveOnServer : true } as your resolverOption param to resolve your query on the server every time the query is executed
  • onSuccess(function) — the function that is passed as a parameter to the onSuccess function will be called when the query returns from the server.

Build a query with the query builder like so: the following query with query for objects of the type $MyObject that have the field status that is equal to Active

var myQuery = Facade.Builders.Query('$MyObject')
                .type('$MyObject')
                .param('oql' , 'status="Active"')
                .registerAs('ActiveObjects');

Let's show an example of a using the Query Builder to build a reusable query. For example, let's say we want to execute a query when the application starts with certain default parameters. In this application, the user can alter the parameters and then click a button to refresh the datalist by querying the server using the new parameters

var default_oql = '...'
var myQuery = Facade.Builders.Query(my_object)
                .param('oql' , default_oql)
                .registerAs('myDatalist');
//On load of the application, execute the query using the default parameters
Facade.Behaviors.App.preLoad(function(){
    return myQuery.execute();
});
//Now, when a button is clicked, we want to re-run the query using the new
//parameters
Facade.Components.Button.forName(...).setOnClick(function(){
    var newOqlStr = '...';
    myQuery.param('oql' , newOqlStr);
    myQuery.execute({resolveOnServer : true});
});

We can also attach an onSuccess() function if we need to chain queries or logic

myQuery.onSuccess(function(results){
    //perform logic based on results
});
Data Buffering

Data buffering prepares your app for the production reality of potentially huge data sets. Query results coming back in FEF may be incomplete. They might only contain the first 100 results (FEF's default). FEF has tools to automate buffering on the client side.

Why buffer? Have you ever tested your app with thousands of rows in each potential query? Often times stress testing gets overlooked during development since realistic sample data can be hard to come by. Large data loads are common in production and can potentially overload the browser if not the backend as well.

Since there is usually less data during development, query results always seem to be of type DataList (since the full result set is returned). But in production or whenever your result set is large, the server clips the results, you get a BufferedList, and the result metadata indicates to the client that the results are buffered. Therefore, it's critical that your code be compatible with both data types. Luckily, both DataLists and BufferedLists are mostly the same in terms of their accessor methods, and that means your app doesn't always have to be aware of which type of result it has. Be careful to never iterate query results with the assumption that you have them all.

Always iterate using forEach() function. DataList will iterate every row, starting at index 0, and BufferedList will automatically iterate only the rows it has, starting at the first index it has and potentially skipping indexes it doesn't have; it can even happen that there are gaps between the windows of data in the list. Never use a for loop - not only can there be gaps in buffered data, but merely visiting an unresolved row will automatically begin resolving it on the server - your app will likely be stuck in an infinite loop as it attempts to keep re-querying more data than the maxLoadedRow.

FEF queries offer buffering arguments in the form of a BufferingStrategy. For example...

Facade.Behaviors.BufferingStrategy.forKind("workItemsBufferingStrategy")
    .setFetchSize(200) // how many rows at a time
    .setPrefetchThresholdRatio(0.45) // when reaching this percentage toward the end of the buffer, we fetch more
    .setMaxLoadedRows(1000) // how many rows stay in memory at once before being garbage collected
    .setUnloadChunkSize(400) // how many rows to garbage-collect at once

Next, pass it to the query using one of the forms of query requests:

Facade.Builders.Query("MyQuery")...bufferingStrategy("workItemsBufferingStrategy")
Facade.Resolvers.query("MyQuery", {..., bufferingStrategy: "workItemsBufferingStrategy"})                   
Resolver Find

Find an object instance through the API

function find(resource, params, resolverOptions)

This method returns a Promise - thus, you can utilize the .then( function(result){}) method to perform an action once the promise has been resolved

Resource The global identifier of the object you are updating
Params or Id Either the UID of the instance you are fetching or an object with an id field
ResolverOptions An object that can contain the following fields:
  • sessionManaged — Determines whether the DataRegistry will keep track of the data returned, i.e. if the query results are changed, will the data be marked dirty to prompt the end-user to save
  • bufferingOptions — An object that can contain the following fields: startIndex and fetchSize. This can offset or limit the number of results, respectively.
Example — Search for Specific OrderDetails

Find a Purchase Order and add it to your DataRegistry

//Find a Purchase Order will a known UID then add it to your DataRegistry

var UID = '...';

Facade.Resolver.find('OrderDetail' , { id : UID } )
        .then( function(instance) {
                Facade.DataRegistry.register( 'poInstance', instance );
        });
Create New Object
NOTE: This is deprecated - please use the method described here to create a new object!

A raw POST request can be sent to the server using the API.flush() method. This can be useful if you need to create a new primary object within your Fef application.

function flush(resource,headers,data)
Resource The global identifier of the object you are creating/updating
Headers Set the headers for the POST request. The Content-Type is by default set to application/json, so it does not need to be set. Set the fingerprint in this object parameter if you are updating an existing object; although this is strongly NOT recommended as best practice( for saving data, use Resolver.persist or save strategy ).
Data Pass in data to create/update the object instance on the Platform

In the below example, we will create an instance of an object. The API.flush method returns a promise, so you can add a .then() function to access/modify the data returned from the server request.

var new_objects_gid = "$MyObjectS1";

Facade.Behaviors.App.onLoad(function(){

    //create your payload for new object - your payload must be a
    //Data Prototype
    var newDataObject = new Facade.Prototypes.Data({
        "someDate" : "05-12-2016",
        "comments" : "Object created by Fef - magical",
        "type": new_objects_gid
    } , { type : new_objects_gid )

    //Create new Instance - return this promise so execution is halted until completed
    return Facade.API.flush(new_objects_gid, null, newDataObject)
        //Success Callback
        .then(function(object_instance) {
            Facade.DataRegistry.register('newObjectInstance' , object_instance);
        },
        //Failure callback
        function(fail) {
            console.log('object failed to create');
        });
}) 
Other Useful Method

Here is a list of other useful Resolver methods.

FunctionSyntax
Transition Data

Transition data in the background of an application. The workflow action is passed as the second parameter and is prefaced by wf_. For example, to transition data using the workflow action go, the second parameter would be wf_go.

function transition(data, workflow_action)
//Get instance data from Data Registry
var data = Facade.DataRegistry.get('someData');
//transition data
Facade.Resolver.transition(data , 'wf_action');
Fetch Type of MasterData

Find type of masterdata.

function queryMasterData(type,resolverOptions)
var masterDataTable = Facade.Resolver.queryMasterData('custom object type', resolverOptions)
Save Data Save data function persist(data,options)

Options can either be true or false(default); indicating whether to automatically persist other roots that are dirty.

//Save primary data
var primaryData = Facade.PageRegistry.getPrimaryData();
Facade.Resolver.persist(primaryData).then( function(res) {
    console.log('primary data saved');
}); 
Query for Attachments

Query for an object's attachments - will return a list of attachments.

function queryAttachments(type,id,resolverOptions)
Facade.Resolver.queryAttachments('$myObjectS1' ,
                                  my_obj_uid ,
                                  resolverOptions )
Query for Organization

Query for organizations.

See above for explanation on queryOptions and resolverOptions. function queryOrganization(queryOptions, resolverOptions)
Query for Object's ActionHistory

Utility method for fetching an object's actionhistory.

See above for explanation on queryOptions and resolverOptions. function queryActionHistory(type, id , resolverOptions)
Find Data's ActionSet

Utility method for fetching data's actionset.

function findActionSet(data, resolverOptions)
Load an Objects Design

Preload a specific design - design will automatically be put in the DesignRegistry when the request completes

function design(design)