Behaviors

The Behaviors entry point of the Facade allows a FEF developer to manipulate the specific behaviors of various parts of the application. A FEF developer can do things like insert methods into the UI lifecycle of the FEF application, alter the saveStrategy, alter the actionStrategy, and much more. A complete list of the different components of FEF behaviors is below, followed by sections for each component that dive into the flexibility and power that these behaviors provide to an application developer.

Behavior Registration

Here is some information in the theory of understanding behaviors within a FEF application.


  • All three categories of include - each defines behavior registration functions.
  • App-defined behavior definition functions are those in the "behaviors" section of the includes.json file.
  • Behavior registration functions are executed in the order they were included.
  • Behaviors are defined for "extensible objects".
    • Components are one example of an extensible object.
    • Every extensible object has a definition and a runtime.
      • Definition is the set of behaviors applied to the extensible object.
      • Runtime is an application's instance of the extensible object.
  • Some examples of behaviors that might be defined (not an all encompassing list below!):
    • Component definitions (ie: Facade.Components.Docbar.setLabel(...)
    • Strategy definitions
      • Data Strategy
      • Save Stratgy
      • Buffering Strategy
    • Function registrations
      • Facade.FunctionRegistry.register("my.function.name", function(behaviorFn, args) { ... });
    • Trigger registrations (will only run at specified timing in app lifecycle)
      • Facade.Behaviors.App.onLoad(function(behaviorFn, args) { ... });
  • Behaviors that are describing a non-function value such as a string or integer can be supplied statically or dynamically.
    • Statically meaning passing the primitive value itself, ie: Facade.Behaviors.Section.forName("mySection").setLabelKey("mySectionLabel");
    • Dynamically meaning passing a function that will calculate the primitive value: Facade.Behaviors.Section.forName("mySection").setLabelKey(function(behaviorFn, args) { return "mySectionLabel"; });
  • Behaviors describing a function value should always be passed as a function.
  • Behavior definitions can be limited to a subsection of extensible objects using the following filters:
    • forName - limits the definition to one instance of the extensible object with the given name (if multiple exist with that name, applies to the first one)
    • forKind - limits the defintion to all extensible objects labeled with the given "kind".
    • forPage - limits the definition to extensible objects only when we are on the given page
    • forType - limits the definition to extensible objects only when the path is pointing to the given type
    • forPath - limits the definition to extensible objects only when the path is pointing to the given type and path
  • Runtimes are generated later in the lifecycle, but will be defined based on behaviors registered here and in the core code.
    • The behaviors used by runtimes are based on, in order:
      • Behaviors defined in the page template itself
      • specificity (see filters)
      • when it was defined (order in includes.json and in script itself)
Behavior Components

Behaviors can be specific to your whole application, a page of your application, or a component of your application.

Behavior Purpose
App Allows FEF developer to insert behavior throughout the lifecycle of the application. For more information on FEF lifecycle, see here.
ActionStrategy Allows the FEF developer to manipulate the actionSet of an application or a specific object instance. One can add an action, hide an action, combine actions of multiple roots, etc.
ActivityStrategy Allows the FEF developer to modify what can send actions to the user interface.
BufferingStrategy Determines how remote data is loaded from the server.
DataCoordinator Allows a FEF developer to add specificity to how specific data is handled throughout the application.
DataFactory Convenience methods for creating/modifying specific types of data within your application.
Page Allows a FEF developer to hook into the lifecycle of a specific page, among other page specific behaviors.
SaveStrategy Behaviors regarding the persistence of your data to the server.
App

App behaviors allow a FEF developer to insert functionality into the FEF UI life cycle. Functionality can be inserted into the preLoad function or onLoad functions as the App loads, and into the preDestroy and onDestroy functions as the App closes.

PreLoad Function

The preLoad function is the apps first interaction with the server. Most of your data should be loaded here. However, it is important to note that the app's primaryData is not available here.

If your preLoad function returns a promise, the app will halt execution until the remote data is resolved from the server so that the data will render.

Facade.Behaviors.App.preLoad( function(){
    //OPTIONAL - return resolving data to force the application
    //to wait until data is resolved before it renders
    return Facade.Resolver.query(...);
});

OnLoad Function

The onLoad function is your applications first access to your app's primaryData.

If your onLoad function returns a promise, the app will halt execution until the remote data is resolved from the server so that the data will render.

Facade.Behaviors.App.onLoad( function(){
    //OPTIONAL - return resolving data to force the application
    //to wait until data is resolved before it renders
    return Facade.Resolver.query(...);
});

PreDestroy Function

The preDestroy function allows the ability to cancel the destruction of the application.

If your preDestroy function returns a promise, the app will halt execution until the remote data is resolved from the server.

Facade.Behaviors.App.preDestroy( function(){
    //OPTIONAL - return resolving data to force the application
    //to wait until data is resolved before it renders
});

OnDestroy Function

The onDestroy function is the hook to complete final cleanup for your application.

If your onDestroy function returns a promise, the app will halt execution until the remote data is resolved from the server.

Facade.Behaviors.App.onDestroy( function(){
    //OPTIONAL - return resolving data to force the application
    //to wait until data is resolved before it renders
});

SetPages Function

The setPages function allows a FEF developer to dynamically create the pages in an application. By default, a FEF application reads its pages directly from its pages.json file. However, if a FEF developer has a FEF application that has a variable number of pages; use this method.

The setPages method returns an array of Pages that must be registered in the PageRegistry. Get a Page from the PageRegistry by using the Facade.PageRegistry.get('page'); syntax.

Setting an app's pages can be useful when using the Wizard archetype if you have an unknown number of wizard pages.

//Set your apps pages
Facade.Behaviors.App.setPages( function(behaviorFn) {
    var pages = [];
    pages.push( Facade.PageRegistry.get('Fef Testing') );
    pages.push( Facade.PageRegistry.get('End') );
    return pages;
});

SetStartingPage Function

By default, a FEF application starts on the page indicated by the start : true field added to a page within your pages.json file. However, if need be, you can set your starting page programmatically.

Here, return a page from your PageRegistry to start your application on:

//Set your apps starting page
Facade.Behaviors.App.setStartingPage( function() {
    var myStartingPage = Facade.PageRegistry.get('myPage');
    return myStartingPage;
});
ActionStrategy

FEF allows the ability for the application to manipulate the actions that can be taken from the user interface. It is important to note that the actions of an application are displayed as buttons in your apps docbar. Actions that are added to your apps actionSet are thus shown in the docbar within the actions group.

You can manipulate which roots' action's are represented in the docbar by setting the coordinated roots of your docbar.

By default, any root that can be modified in your FEF application will have its actionSet reflected in the docbar. For multi root apps, it is common to customize this portion of your application to specify and make clear which action is coordinated with its corresponding root.

Facade.Behaviors.DataCoordinator.actionStrategy().setCoordinatedRoots(function() {
    //return array of roots to include in your docbar - the combination of all the roots
    //will have their actions represented in the docbar
    return [...];
  }
);

If you wish to hide or add action buttons from the docbar, it is best practice to manipulate the actionSet of the application instead of attempting to hide the button directly. The docbar interprets the data of your application; therefore, when you manipulate the data ( in this case, the actionSet ), the docbar will render accordingly.

The global app's actionSet() can be accessed either through the DataCoordinator - Facade.Behaviors.DataCoordinator.actionStrategy().actionSet() or through the DataStrategy - Facade.Behaviors.DataStrategy.actionSet()

Each fully loaded instance in your application will have an actionSet associated with it (fully loaded meaning fetched via the API; not queried). At runtime, you can access the actionSet of an instance(using PrimaryData as an example)

var rowObj = Facade.PageRegistry.getPrimaryData();
//array of actions within your primary datas actionset
var primaryDataActionSet = rowObj.getDataStrategy().getActionSet();

Set App's ActionSet Function

Access the app's actionSet to override your DataCoordinator's setActions method of your actionSet. In this method, access the apps actionSet by calling behavior.resume().

Return an array of actions from this method. The array of actions will be your app's actionSet.

Here, return a page from your PageRegistry to start your application on:

Facade.Behaviors.DataCoordinator.actionStrategy().actionSet().setActions(
function(behaviorFn){
        var actions = behaviorFn.resume();
        if(actions){
            var data = Facade.PageRegistry.getPrimaryData();
            //Alter your actionSet
        }
        return actions;
    });

If you only need to append an action to your actionSet, you can use the appendActions method of the actionSet():

//append action clientValidationAction
Facade.Behaviors.DataCoordinator.actionStrategy().actionSet().appendActions( 
    function(){
        return 'clientValidationAction';
    });

Access a Specific Action

Access an action of the actionSet by its name; using the action('str') method. If your action is a workflow action, access it using wf_action

var someAction = Facade.Behaviors.DataCoordinator.actionStrategy().actionSet().action('myAction');

You can also change the actionType of your action and change what happens when the action is triggered through your FEF UI. Your action is triggered onClick of its button in the action group by default.

//Set Action as a transition type of action
someAction.setActionType( Facade.Constants.ActionType.TRANSITION );
//Set functionality to occur onClick of your action
someAction.setExecute( function(behaviorFn,args){
    //Occurs when action is triggered
});

To run code after your transition function executes, do something like the following

someAction.setExecute(function(behaviorFn,args){
    return behaviorFn.resume().then(function(){
        //perform functionality post-transition here
    });
});

Manipulate ActionSet for a Page in your App

If you want to manipulate the actionSet for a specific page, use the following forPage technique. This will allow the FEF developer to only modify an actionSet for a specific page rather than across your entire application.

//Get the actionSet for page startingPage
Facade.Behaviors.DataCoordinator.actionStrategy().forPage('startingPage').actionSet()

Set ActionStrategy Coordinated Roots

Your app's actionSet() draws its actions from the actionSet() of each of your apps coordinated roots. In order to explicitly set your coordinated roots, utilize the setCoordinatedRoots function of your Action Strategy. Here, we return an array of data to serve as the roots to our app's actionSet. The data must be registered in the Data Registry. Your app's actionSet can draw from any instance data in your application.

In the code example, let's say we want our app's actionSet to be actions from our primaryData and from a primary object that we loaded into our application and stored in the DataRegistry using the key otherPrimaryRoot. Here, we will add that otherPrimaryRoot to our actionSet. Thus, our docbar will have buttons for all of our actions in our actionSet.

Facade.Behaviors.DataCoordinator.actionStrategy().setCoordinatedRoots(
    function(behaviorFn, args) {
	    var roots = [];
	    roots.push(Facade.PageRegistry.getPrimaryData());
	    roots.push(Facade.DataRegistry.get('otherPrimaryRoot');
	    return roots;
    });

Set ActionStrategy Coordinated Roots

Set an action as tasked by returning the action in the setTaskedAction method. A tasked action displays as an emphasized button on the docbar.

Facade.Behaviors.DataCoordinator.actionStrategy().setTaskedAction(function(){
    return '<your action you want tasked>';
});

Accessing ActionStrategy at Runtime

Your applications current actionSet can be accessed during the runtime of your application by dealing directly with the DataCoordinator. For example, let's say that we want to set a components mask depending on our current actionSet.

var component = Facade.Components.Button.forName(...);
component.setMask(function(behaviorFn,args){
    //get actionSet runtime of your app
    var actionsetRuntime = Facade.DataCoordinator.getActionStrategy().getActionSet();
    //or
    //access a single root's actionset
    var root = Facade.DataRegistry.get('...');
    var actionsetRuntime = root.getDataStrategy().getActionSet();
});

Note that we are not going through the Behaviors endpoint - we directly deal with Facade.DataCoordinator

Once you access the actionSet runtime, the following functions could be useful

  • Get Actions — returns an array of all your current actions in your actionSet
    var myActions = actionsetRuntime.getActions();
  • Get Action — get a specific action of your actionSet
  • //Get a workflow action named start
    var myAction = actionsetRuntime.getAction("wf_start");
    //get actiontype
    myAction.getActionType();
    //execute the action - will return a promise while the action executes
    myAction.execute();
  • Get PDF Actions — get list of pdf actions associated with an actionSet
    var pdfActions = actionsetRuntime.$getPdfActions();
    //Execute first pdf action at runtime
    actionsetRuntime.getAction( pdfActions[0] ).execute();
ActivityStrategy

The ActivityStrategy controls the activities that are sent to the UI to alert the user when certain activities occur. By default, the following activities are reported to the user : save, transition, attach, detach, and app loaded.

Set Coordinated Roots

Overrides default coordinated roots that produce activities to your FEF UI

Facade.Behaviors.DataCoordinator.activityStrategy().setCoordinatedRoots(function(){
    var roots = [];
    //add roots
    roots.push( Facade.DataRegistry.get('someData') );
    return roots;
});

Throw a Custom Activity to UI

Along with the default activities that FEF shows on the UI, a FEF developer can throw a custom activity to the UI. A possible purpose of such an activity could be requesting information from the server during user interaction with your application. Perhaps we want to alert the user that we are requesting information and then give a message once the data is resolved.

Firstly, we want to add a custom action to our activityStrategy. Use the appendActivityCodesReportedToUser() method to explicitly indicate to FEF that the activity we are able to generate should be reported to the UI

Facade.Behaviors.DataCoordinator.activityStrategy()
                            .appendActivityCodesReportedToUser(['someAction']);

Now, any activity that we generate that has activityCode : someAction will be shown to the user

Let's say that we want to throw a custom activity when a user clicks a button. An activity is made up of steps. Each step in the activity must be another activity or a promise. In this case, we will add one step to our activity and pass it a promise. Once the activity is added to our ActivityLog it will complete the promise. The following are common parts of an activity:

  • activityCode — indicates the activty code of the promise. If you are creating a custom activity, this should be the same as the code you added to your activityStrategy to be reported to the user
  • text — the text that displays while the steps of the activity are resolving
  • relatedTo — data that is related to the promise
  • resultText — text that displays when activity finishes
  • status — status of the activity - usually either SUCCESS or FAILURE

Here is an example of an activity that searches for users and shows the user

//On click of the button, trigger the activity
Facade.Components.Button.forName('myBtn').setOnClick(function(){
    var data = Facade.PageRegistry.getPrimaryData();
    //Set the activites code, status, text,and relatedTo data
    var activity = Facade.ActivityLog.add(new Facade.Prototypes.Activity(undefined,{activityCode: 'someAction', relatedTo: data, text: "Resolving"}))

    var promise = Facade.Resolver.query('User');
    //Add a step to the activity - on callback of the promise, set the activities
    //status to success and set its result text
    activity.addStep( promise.then(function(res){
        activity.setResultText("Its all done");
        activity.setStatus(Facade.Constants.ActivityStatus.SUCCESS);
    }));
});

When your activity completes, you will get the following message on the screen:

Successful Activity
Successful Activity

Throw An Error Message Activity

Here is an example of how to throw an error message to the UI. In this example, we are essentially setting up a client-side data validation. We want to throw an error if a field, named name, is not populated. We will perform this validation check when the user clicks the save button.

Notice below that we are intercepting the save strategy's setExecute() function. If the name field is filled out, we simply call behaviorFn.resume() to resume the default save strategy.

// Perform validation on click of save button
Facade.Behaviors.DataCoordinator.saveStrategy().setExecute(function(behaviorFn, args) {
    var errorMsg = "Trading Partner Name is a required";
    var rootData = Facade.PageRegistry.getPrimaryData();
    if (! rootData.get('name') ) {
        var activity = Facade.ActivityLog.add(new Facade.Prototypes.Activity(undefined, {
            activityCode: Facade.Constants.ActivityCode.SAVE,
            relatedTo: rootData,
            text: "Save"
        }))
        //Add a message, giving it the code of type ERROR so the UI
        //will display the error in red
        activity.addMessage(new Facade.Prototypes.Message(undefined, {
            messageType: Facade.Constants.MessageType.ERROR,
            text: errorMsg,
            relatedTo: rootData
        }))
        //Throw the activity to the UI
        Facade.FunctionRegistry.execute("core.activityLog.onUIActivityComplete", {
            activity: activity
        })
    } else {
        //If the name is properly set, resume the behavior. In this case, the default
        //behavior is to execute the saveStrategy of your application
        return behaviorFn.resume();
    }
});
BufferingStrategy

Your app's BufferingStrategy determines how data is loaded from the server. FEF developers have the ability to alter certain aspects of the Buffering Strategy.

The BufferingStrategy can be accessed through the facade by:

Facade.Behaviors.BufferingStrategy 

When you define a BufferingStrategy, give the strategy a kind and then reference the kind when applying the buffering strategy to a query.

Facade.Behaviors.BufferingStrategy
   .forKind('myBufferingStrategy')
   .setFetchSize(50);
//Then in your query builder, refrence the defined strategy
Facade.Builders.Query(...)
   .bufferingStrategy('myBufferingStrategy')

Query results can return an incomplete result set. For example, the default maximum results is 100. If a query returns more than 100 results, the result set will be considered incomplete. Incomplete result sets are handled with Buffered Data in a FEF application. All Data API queries do this by default; that is, handle large result sets with buffered data. Buffered Lists abstract away the task of dealing with buffered data.

Here is a list of methods that can be used to override the default FEF BufferingStrategy.

Method Purpose Default FEF Value
setFetchSize(int) The number of results to pull at once. This number should always be larger than the viewport size. It must not be larger than the API's max supported fetch size. 100
setPrefetchThresholdRatio(decimal) When the viewport moves within this percentage of the end of the result, more results will be pre-fetched. Set to 0 if no pre-fetching is desired beyond the viewport. Must be less than 1. 0.25
setMaxLoadedRows(int) The maximum number of results that can be in memory at one time. The number should always be larger than the fetchSize. 500
setUnloadChuckSize(int) The number of results to discard at one time when maxLoadedRows is exceeded. When maxLoadedRows + unloadChunkSize is greater than or equal to the number of results, we discard this number of results. 200
A function can be inserted as well into each of the above methods.
DataCoordinator

The DataCoordinator is the place to go to modify the default data behavior of your FEF application. The DataCoordinator gives the FEF developer access to the actionStrategy, activityStrategy, and saveStrategy. See each strategy's corresponding sections for more information and syntax.

DataFactory

The DataFactory allows an easy way to create data objects of a certain type. To create a new data object, use the create({ type : ... , data : ... }) method. The create method takes one parameter: which is an object that has a type field and a data field. The type field indicates what type to create the new data object as and the data field is the data of your new data object.

A use case for the DataFactory is creating an object that belongs in a table that holds embedded objects. The following example shows how to add an item to a table: the table holds a list of embedded objects. In this example, the new object is created onClick of a button.

var myButton = Facade.Components.Button.forName('addObjectBtn');
myButton.setOnClick(function(behaviorFn,args){
    var embeddedObjTable = Facade.PageRegistry.getComponent('myTable');

    var newObject = embeddedObjTable.getDataFactory().create({
        type: embeddedObjTable.getPathData().getDesignField().getDesignType(),
        data: {
            fieldX : 'Newly created object' ,
            fieldY : ...
            ...
        }
    })
    embeddedObjTable.addItem(newObject);
}) 

We can also use the DataFactory in a most generic way. Let's say we create an object in our application and every time we create the object we want to add a licensee party by default and set a field named createdBy to the value of FefApp. We can use the DataFactory and bind it to this object type to auto-populate this logic each time a new instance of this object is being created from the app.

You must always return the manipulated data object from the below function
Facade.Behaviors.DataFactory.forType('<ObjectGlobalIdentifier>').setCreate(function(behaviorfn){
    var newData = behaviorfn.resume();
    newData.set('licensee' , {
        memberId : Facade.DataRegistry.get('$currentOrg').getOrganizationId()
    });
    newData.set('createdBy' , 'FefApp');
    return newData;
});
Page

Similar to what can be done with your App as a whole in terms of hooking into life cycle events, the Page entry point from the Facade allows for code to run when certain events occur. These events are : preLoad , onLoad , preDestroy, and onDestroy. Insert code into the life cycle of a specific page by doing the following:

//Insert code into preLoad event of page called aPage
//In this case, aPage is the key for a page in your PageRegistry
Facade.Behaviors.Page.forPage('aPage').preLoad(
    function(behaviorFn,args){
        //put code here
        //OPTIONAL - return promise to halt page render
        //until promise resolves
        return Facade.Resolver...
    });

//Access each page of your application using the args param
Facade.Behaviors.Page.preLoad(
    function(behaviorFn,args){
        //this page that is loading
        var page = args.page;
        //logic
});

When loading remote data, return a Promise from the event functions

Here is a quick explanation of each event in your pages lifecycle. More information is available on the FEF Lifecycle page.

Event Purpose
preLoad Most of your remote data should be loaded here before the page renders.
onLoad Modify preLoaded page data. Set primaryData if not already set.
preDestroy Fires before page is destroyed. Navigation can be stopped at this point.
onDestroy Final cleanup of a page. Navigation has completed and thus cannot be halted here.
SaveStrategy

The SaveStrategy determines how the save button in your docbar functions. The save button relies on the saveStrategy to determine whether it is enabled and how to act to an onClick. Similar to other FEF concepts, parts of the saveStrategy can be overridden to fulfill the desire of the FEF developer. The save button in the docbar is enabled when any of the candidates of your saveStrategy has dirty data. By default, the only candidate of your app is your primaryData. The method that determines this is the isDirty method of your saveStrategy, described below. Once your save button is enabled and clicked by the user, the setExecute() method of your saveStrategy runs. The setExecute method, by default, looks at all of your candidates of your saveStrategy and persists any dirty data back to the server. Once a save completes, you will see a successful save activity appear within your application and your data will now be considered clean, as it mirrors the data on the server.

Below is a list of common ways to modify a saveStrategy to fit your apps needs

Add Types of Candidates to your Save Strategy

Let's say we have multiple roots in our application and we need to persist any changes in either root to the server. To do this, add candidates by their instance type to your saveStrategy using the following method. This overrides the default candidate structure - so add all of the types that need to be persisted within your application.

Facade.Behaviors.DataCoordinator.saveStrategy().candidates().
                            forType('$FefTestQuestionQ1', '$FefTestDesign1S1');

The above code indicates to the saveStrategy to look for any data in your data registry that is of type $FefTestQuestionQ1 or $FefTestDesign1S1. Now, if any data that is of that type is modified and is thus dirty, your save button in the docbar will be enabled and onClick will persist changes to the correct object instances automatically.

Set Specific Candidates to your Save Strategy

Let's say we have multiple roots of our app but we only want to persist changes to certain pieces of data to the server. We can do this by explicitly setting the candidates of our saveStrategy using the setCandidates method. When overriding this method, return an array of data prototype objects that are registered in your Data Registry that you want to persist to the server. Once this method is overridden, your saveStrategy will only check these candidates for dirty-ness

Facade.Behaviors.DataCoordinator.saveStrategy().setCandidates(
    function(behaviorFn,args){
        var candidates = [];
        //add primaryData to your candidates list
        candidates.push( Facade.PageRegistry.getPrimaryData() );
        return candidates;
    });

Similarly, add candidates to your list of candidates by utilizing the appendCandidates.

Facade.Behaviors.DataCoordinator.saveStrategy().
                               .appendCandidates([...]);

Override Save Button Dirty Check Method

As mentioned above, the save button becomes enabled when it determines that the data that needs to be persisted is dirty. It does that with the setIsDirty() method of the saveStrategy. This method returns true or false depending on the dirty state of the data. This can be overridden by the FEF developer.

Facade.Behaviors.DataCoordinator.saveStrategy().setIsDirty(
    function(behaviorFn,args){
        //return boolean: true or false
    });

Override Save Button OnClick function

As mentioned above, the save button in the docbar fires the setExecute() method of your saveStrategy. In rare cases, there could be a use-case where this method would need to be overridden.

Facade.Behaviors.DataCoordinator.saveStrategy().setExecute(
    function(behaviorFn,args){
        //Persist the data
    });