Inline Views

Inline Views and related functionality (like insights) provide a lot of what makes the Ultralink powerful. They can automate and abstract away complex processes that are normally manual, fragile and time consuming. Even though they can achieve extremely complex tasks, implementing Inline Views is actually quite simple.

Invocation

When a user clicks on a black shadow link in an Ultralink, the code checks to see if the view attribute for the link type is set to 'true'. If it is, then it starts the process of setting the link type's Inline View up. It locates the link type's directory located in the Ultralink Root it was defined in (see Ultralink Roots for more on how a Root is structured) and creates an iframe for a file named view.html located in it. It passes various bits of information to this page through URL parameters which are automatically picked up and interpreted (you don't need to worry about reading them directly).

The contents of most view.html files look something like this:

<!DOCTYPE html>
    <head>
    </head>
	<body>
        <script type="text/javascript" src="https://ultralink.me/linkTypes/_common/common.js"></script>
    </body>
</html>

This is intentionally very sparse. The key here is to make sure that common.js is loaded from an Ultralink Server (or similar location). If your Inline View's functionality makes use of other JavaScript or CSS files then you can include them here.

common.js

common.js is the file that interprets all the incoming information and appropriately bootstraps the rest of the Inline View. It parses the incoming URL arguments and pulls out information like the link URL, insight data and Ultralink version among other things. It performs some basic environmental tests and then proceeds to load dependencies.

Early in this process it loads view.css where you can store CSS specific to this Inline View. The very last file loaded is your view.js file which is where the bulk of the link type functionality resides. The last thing that common.js does is to setup a global variable for itself (Ultralink.InlineView) through which it exposes data and fuctionality. Below is a list the variables and methods exposed through Ultralink.InlineView.

Core Routing
sendCoreMessage( type, query )This sends a message to the Ultralink Core logic where it is routed accordingly.
sendQuery( command, extras )A convinience method which sends simple queries to the Ultralink Core.
handleCoreMessage( type, result )A placeholder routine that you should fill in if you want to receive messages from the Ultralink Core logic.
Signaling
reportFailure()This signals to the Ultralink that something happened that the the Inline View should not be displayed.
kickoffViewLoad( allowEscape )This signals to the Ultralink that the view has been constructed and is ready for display.
expandIframe( inlineURL, allowEscape )This is a convinenice function for simple iframing after the logic in view.js has had a chance to decide exactly what to iframe.
blueShadowLink( blueShadowURL )Sends a new URL to overwrite the current blue shadow link address so the Inline View can modify and take control of it.
setInlineLink( el )Activates anchor tags so when clicked it loads the Inline Pane for the link in the 'data-url' attribute of the 'data-type' link type.
Helpers
getUrlVars( theURL, uriDecode )Helps to grab parameters passed in the URL.
escapeHTML( text )Esacpes HTML in content to make it safe for display.
sanitizedURL( inURL )Returns a sanitized version of the URL.
sanitizeHTML( dirtyHTML, options )Cleans untrusted HTML input so that it can be safely used.
matchesInsightData( name, field, test )Returns whether a particular set of insight data has been matched.
swapFade( first, second, time )Alternately fades two elements into each other when clicked.
loadUltralinkScript( successCallback )Loads the appropriate version of ultralink.js in case your Inline View needs any of it's functionality.
Setup
browserBrowser detection tests. Member variables indicate brower environment and if there is a touch style interface.
urlTypeThe link type for this Inline View.
URLThe URL for this Inline View.
insightInfoAn object describing which insights were triggered by this link (if any).
ulVersionVersion of the Ultralink code bringing up this Inline View.
environmentOperational environment type for the Ultralink code bringing up this Inline View.
masterDomainThe domain for the Ultralink Server associated with the originating Ultralink.
masterPathThe URL for the Ultralink Server associated with the originating Ultralink.
String Functions
replaceAll( str, find, replace )A handy function that replaces all instaces of the given string.
Formatting Helpers
monthNamesEnglish month names in an ordered array.
truncate( str, length )Optionally truncates a string and appends '...' on the end if it exceeds a specified length (defaults to 80).
decimalPlaces( number )Returns the number of decimal places in a number.
truncateDecimals( num, maxPlaces )Truncates a number to a specific number of decimal places.
addNumericCommas( num )Adds commas to large numbers in order to make them easier to read.
deFluff( s )Removes common english punctuation, white space and makes it lower case so we can make some logical comparisons.
amountFormatter( amount, currency )Produces a nice looking numeric string with an optional currency prefix.
currenciesReturns the appropriate currency symbol for the given currency code.
currencySymbol( currency_code )Given a currency code, returns the corresponding currency symbol or an appropriate string.
Formatting Canonizers
canonizeOrganization( org )Returns a clean, canonized organization string.
canonizeOrganizationAbv( org )Returns a clean, canonized organization abbrivation string.
degreeSimplifiersSome replacement sets to make the canonization of a degree string easier.
degreesA non-exhaustive list of degree types and their abbreviations.
canonizeDegree( deg )Returns a clean, canonized degree abbrivation string.
studySimplifiersSome replacement sets to make the canonization of a study string easier.
studiesA non-exhaustive list of various disciplines and their abbreviations.
canonizeStudy( stud )Returns a clean, canonized study abbrivation string.
canonizeTitle( ti )Returns a clean, canonized title string.
canonizePerson( inv )Returns a clean, canonized name string.
canonizeCompany( inv )Returns a clean, canonized company string.
Core Routing
sendCoreMessage( type, query )
This sends a message to the Ultralink Core logic where it is routed accordingly.
sendQuery( command, extras )
A convinience method which sends simple queries to the Ultralink Core.
handleCoreMessage( type, result )
A placeholder routine that you should fill in if you want to receive messages from the Ultralink Core logic.
Signaling
reportFailure()
This signals to the Ultralink that something happened that the the Inline View should not be displayed.
kickoffViewLoad( allowEscape )
This signals to the Ultralink that the view has been constructed and is ready for display.
expandIframe( inlineURL, allowEscape )
This is a convinenice function for simple iframing after the logic in view.js has had a chance to decide exactly what to iframe.
blueShadowLink( blueShadowURL )
Sends a new URL to overwrite the current blue shadow link address so the Inline View can modify and take control of it.
setInlineLink( el )
Activates anchor tags so when clicked it loads the Inline Pane for the link in the 'data-url' attribute of the 'data-type' link type.
Helpers
getUrlVars( theURL, uriDecode )
Helps to grab parameters passed in the URL.
escapeHTML( text )
Esacpes HTML in content to make it safe for display.
sanitizedURL( inURL )
Returns a sanitized version of the URL.
sanitizeHTML( dirtyHTML, options )
Cleans untrusted HTML input so that it can be safely used.
matchesInsightData( name, field, test )
Returns whether a particular set of insight data has been matched.
swapFade( first, second, time )
Alternately fades two elements into each other when clicked.
loadUltralinkScript( successCallback )
Loads the appropriate version of ultralink.js in case your Inline View needs any of it's functionality.
Setup
browser
Browser detection tests. Member variables indicate brower environment and if there is a touch style interface.
urlType
The link type for this Inline View.
URL
The URL for this Inline View.
insightInfo
An object describing which insights were triggered by this link (if any).
ulVersion
Version of the Ultralink code bringing up this Inline View.
environment
Operational environment type for the Ultralink code bringing up this Inline View.
masterDomain
The domain for the Ultralink Server associated with the originating Ultralink.
masterPath
The URL for the Ultralink Server associated with the originating Ultralink.
String Functions
replaceAll( str, find, replace )
A handy function that replaces all instaces of the given string.
Formatting Helpers
monthNames
English month names in an ordered array.
truncate( str, length )
Optionally truncates a string and appends '...' on the end if it exceeds a specified length (defaults to 80).
decimalPlaces( number )
Returns the number of decimal places in a number.
truncateDecimals( num, maxPlaces )
Truncates a number to a specific number of decimal places.
addNumericCommas( num )
Adds commas to large numbers in order to make them easier to read.
deFluff( s )
Removes common english punctuation, white space and makes it lower case so we can make some logical comparisons.
amountFormatter( amount, currency )
Produces a nice looking numeric string with an optional currency prefix.
currencies
Returns the appropriate currency symbol for the given currency code.
currencySymbol( currency_code )
Given a currency code, returns the corresponding currency symbol or an appropriate string.
Formatting Canonizers
canonizeOrganization( org )
Returns a clean, canonized organization string.
canonizeOrganizationAbv( org )
Returns a clean, canonized organization abbrivation string.
degreeSimplifiers
Some replacement sets to make the canonization of a degree string easier.
degrees
A non-exhaustive list of degree types and their abbreviations.
canonizeDegree( deg )
Returns a clean, canonized degree abbrivation string.
studySimplifiers
Some replacement sets to make the canonization of a study string easier.
studies
A non-exhaustive list of various disciplines and their abbreviations.
canonizeStudy( stud )
Returns a clean, canonized study abbrivation string.
canonizeTitle( ti )
Returns a clean, canonized title string.
canonizePerson( inv )
Returns a clean, canonized name string.
canonizeCompany( inv )
Returns a clean, canonized company string.

Most of the above functionality is available for your convinience and you can chose to implement similar functionality yourself or use 3rd-party code. What is required however, is that at some point, your code in view.js must signal upstream to the Ultralink code whether or not your Inline View is ready for display. If you do not, then the link's Inline Pane will sit there and spin indefinately.

If something went wrong and your Inline View is either unable to be displayed or should not be displayed, you call Ultralink.InlineView.reportFailure(). This will tell the Ultralink code to abort the load and the Inline Pane's progress spinner will go away.

If setup is successful and your view is ready for display (dependant API calls have returned, UI created, etc.), then you call Ultralink.InlineView.kickoffViewLoad(). This signals that your Inline View is ready to be shown and the Ultralink code can present it to the user.

There is also a convinience method Ultralink.InlineView.expandIframe( inlineURL ) which simply iframes the given URL in a safe, full-framed way and then calls Ultralink.InlineView.kickoffViewLoad(). This can be handy when you want to iframe a variation of a URL that is normally used for linking the site (example: YouTube links vs embedded YouTube links).

That is really all there is to it. You can create full-featured, complex Inline Views that can do anything any web app can do. Try following our step-by-step walkthrough on How To Create Link Types and make your own Inline Views. If you want to take advantage of more complex Ultralink functionality like insights, you will need to move some of your Inline View's functionality over to the message.js file.

message.js

If you want to create Ultralink insights for your link type or if your link type runs in an environment that could benefit from a wider caching scope (like a browser extension), you need to move your API calls into message.js. The basic setup for message.js is like so:

ls.messageHandlerPrep( function() // Makes sure that everything is ready to go.
{
    // Fill in the message handler for your link type.
    messageHandler['mylinktypeQuery'] = function( event, callbackPage )
    {
        var query = event.query; // This object has all the information you will need to execute the command.

        switch( query.command )
        {
            case "MyCacheableCommand": // Getting a user profile, info that doesn't change often, etc.
            case "TwoHopsAway":        // An Ultralink insight command. See more about this below.
            {
                // The information for the API call probably needs to parsed out of query.URL
                query.callURL = "https://someapi.endpoint.com/based/on/" + query.URL;

                // If there is a live, cached result for this API call, then return that instead.
                if( data = APICache.lookup( query.callURL ) )
                {
                    querySuccess( callbackPage, query, data );
                }
                // If not, then actually perform the API call
                else
                {
                    // A standard AJAX method. You can used something else if you prefer.
                    ls.xhrRequest( 'GET', query.callURL,
                        function(data)
                        {
                            // Add the result of this call to the API cache. Make it valid for one day.
                            APICache.insert( query.callURL, data, oneDay );
                            querySuccess( callbackPage, query, data );
                        },
                        function( xhr ){ queryFailure( callbackPage, query ); console.log("error", xhr); }
                    );
                }
            } break;

            case "MyUncacheableCommand": // Performing actions, getting data that changes often.
            {
                query.callURL = "https://someapi.endpoint.com/also/based/on/" + query.URL;

                // Just make the API call and return the result.
                ls.xhrRequest( 'GET', query.callURL,
                    function( data ){ querySuccess( callbackPage, query, data );                      },
                    function(  xhr ){ queryFailure( callbackPage, query ); console.log("error", xhr); }
                );
            } break;
        }
    };
});
ls.messageHandlerPrep( function() // Makes sure that everything is ready to go.
{
    // Fill in the message handler for your link type.
    messageHandler['mylinktypeQuery'] =
    function( event, callbackPage )
    {
        // This object has all the information
        // you will need to execute the command.
        var query = event.query;

        switch( query.command )
        {
            // Getting a user profile,
            // info that doesn't change often, etc.
            case "MyCacheableCommand":
            // An Ultralink insight command.
            // See more about this below.
            case "TwoHopsAway":
            {
                // The information for the API call
                // probably needs to parsed out
                // of query.URL
                query.callURL = "https://someapi.endpoint.com/based/on/" + query.URL;

                // If there is a live, cached result
                // for this API call, then
                // return that instead.
                if( data = APICache.lookup( query.callURL ) )
                {
                    querySuccess( callbackPage, query, data);
                }
                // If not, then actually
                // perform the API call
                else
                {
                    // A standard AJAX method.
                    // You can used something else
                    // if you prefer.
                    ls.xhrRequest( 'GET', query.callURL,
                        function(data)
                        {
                            // Add the result of this
                            // call to the API cache.
                            // Make it valid for one day.
                            APICache.insert( query.callURL, data, oneDay );
                            querySuccess( callbackPage, query, data );
                        },
                        function( xhr )
                        {
                            queryFailure( callbackPage, query );
                            console.log("error", xhr);
                        }
                    );
                }
            } break;

            // Performing actions,
            // getting data that changes often.
            case "MyUncacheableCommand":
            {
                query.callURL = "https://someapi.endpoint.com/also/based/on/" + query.URL;

                // Just make the API call
                // and return the result.
                ls.xhrRequest( 'GET', query.callURL,
                    function( data )
                    {
                        querySuccess( callbackPage, query, data );
                    },
                    function( xhr )
                    {
                        queryFailure( callbackPage, query );
                        console.log("error", xhr);
                    }
                );
            } break;
        }
    };
});
ls.messageHandlerPrep( function() // Makes sure that everything is ready to go.
{
    // Fill in the message handler for your link type.
    messageHandler['mylinktypeQuery'] =
    function( event, callbackPage )
    {
        // This object has all the information
        // you will need to execute the command.
        var query = event.query;

        switch( query.command )
        {
            // Getting a user profile,
            // info that doesn't change often, etc.
            case "MyCacheableCommand":
            // An Ultralink insight command.
            // See more about this below.
            case "TwoHopsAway":
            {
                // The information for the API call
                // probably needs to parsed out
                // of query.URL
                query.callURL = "https://someapi.endpoint.com/based/on/" + query.URL;

                // If there is a live, cached result
                // for this API call, then
                // return that instead.
                if( data=APICache.lookup(query.callURL) )
                {
                    querySuccess(callbackPage,query,data);
                }
                // If not, then actually
                // perform the API call
                else
                {
                    // A standard AJAX method.
                    // You can used something else
                    // if you prefer.
                    ls.xhrRequest( 'GET', query.callURL,
                        function(data)
                        {
                            // Add the result of this
                            // call to the API cache.
                            // Make it valid for one day.
                            APICache.insert(
                                query.callURL,
                                data,
                                oneDay );
                            querySuccess(
                                callbackPage,
                                query,
                                data );
                        },
                        function( xhr )
                        {
                            queryFailure(
                                callbackPage,
                                query );
                            console.log("error", xhr);
                        }
                    );
                }
            } break;

            // Performing actions,
            // getting data that changes often.
            case "MyUncacheableCommand":
            {
                query.callURL = "https://someapi.endpoint.com/also/based/on/" + query.URL;

                // Just make the API call
                // and return the result.
                ls.xhrRequest( 'GET', query.callURL,
                    function( data )
                    {
                        querySuccess(
                            callbackPage,
                            query,
                            data );
                    },
                    function( xhr )
                    {
                        queryFailure(
                            callbackPage,
                            query );
                        console.log("error", xhr);
                    }
                );
            } break;
        }
    };
});

In message.js you define a messageHandler for your link type (in this example the link type is called mylinktype so the command should be mylinktypeQuery). Your view.js file will send it commands and requests that may require API calls or other logic which you handle here.

For each command that comes in, you can pull what you need out of query.URL to perform the command/make the API call, and report back success (querySuccess) with some data or report failure (queryFailure). You can optionally pass back data in the failure case as well.

For commands in which it makes sense, interact with the APICache to increase responsivness and alleviate network congestion. Using the APICache is crucial when using Ultraink insights because insights usually need to hit API endpoints really hard over and over.

So now that our your API calls are being made inside message.js, back in view.js you use Ultralink.InlineView.sendCoreMessage() to send queries to message.js like this:

Ultralink.InlineView.sendQuery("MyCacheableCommand");

And receive the results of your queries like this:

// Set the core messsage handler
Ultralink.InlineView.handleCoreMessage = function( type, result )
{
    switch( type )
    {
        case Ultralink.InlineView.urlType + 'Result':
        {
            switch( result.query.command )
            {
                case 'MyCacheableCommand':
                {
                    if( result.status == 'success' )
                    {
                        // Build the UI using result.data.
                        Ultralink.InlineView.kickoffViewLoad(); // Once you are ready, give the OK to display.
                    }
                    else{ Ultralink.InlineView.reportFailure(); } // We failed to get crucial data for the UI. Abort.
                } break;

                case 'MyUncacheableCommand': // This might be a command that is run after the Inline View is up.
                {
                    // Modify the UI accordingly.
                } break;
            }
        } break;
    }
}
// Set the core messsage handler
Ultralink.InlineView.handleCoreMessage = function( type, result )
{
    switch( type )
    {
        case Ultralink.InlineView.urlType + 'Result':
        {
            switch( result.query.command )
            {
                case 'MyCacheableCommand':
                {
                    if( result.status == 'success' )
                    {
                        // Build the UI using result.data.
                        // Once you are ready, give the OK to display.
                        Ultralink.InlineView.kickoffViewLoad();
                    }
                    else
                    {
                        // We failed to get crucial data for the UI. Abort.
                        Ultralink.InlineView.reportFailure();
                    }
                } break;

                // This might be a command that is run after the Inline View is up.
                case 'MyUncacheableCommand':
                {
                    // Modify the UI accordingly.
                } break;
            }
        } break;
    }
}
// Set the core messsage handler
Ultralink.InlineView.handleCoreMessage =
function( type, result )
{
    switch( type )
    {
        case Ultralink.InlineView.urlType + 'Result':
        {
            switch( result.query.command )
            {
                case 'MyCacheableCommand':
                {
                    if( result.status == 'success' )
                    {
                        // Build the UI using result.data.
                        // Once you are ready,
                        // give the OK to display.
                        Ultralink.InlineView.kickoffViewLoad();
                    }
                    else
                    {
                        // We failed to get crucial
                        // data for the UI. Abort.
                        Ultralink.InlineView.reportFailure();
                    }
                } break;

                // This might be a command that is
                // run after the Inline View is up.
                case 'MyUncacheableCommand':
                {
                    // Modify the UI accordingly.
                } break;
            }
        } break;
    }
}

insights.js

Once you have all your API calls and such routed through message.js with correct caching policies, you are ready to start authoring Ultralink insights! There are two main sections inside every insights.js file. The first deals with defining the set of Ultralinks and links that need to be examined, along with gathering any user requested conditions. The second deals with evaluating the relevant API response to see if the insights has been triggered (taking into account the user's requested conditions if any).

if( typeof insights == "undefined" ){ var insights = {}; }

insights['mylinktype'] =
[
    // ... insight definitions ...
];

First off, the global insights needs to be defined if it isn't already. Next we add an entry under our link type. This entry is the link type that needs authorization supplied for the insights to work (if applicable). It is almost always the same link type as the actual insights. Let's first examine a simple example insight definition.

{ name: 'Trending', linkType: 'mylinktype', title: "Trending Right Now", description: "Organizations, events and people that are trending right now." }
{
    name:        'Trending',
    linkType:    'mylinktype',
    title:       "Trending Right Now",
    description: "Organizations, events and people that are trending right now."
}

The name field is a unique identifier that specifies this particular insight. linkType indicates the type of link that this insight is routed through. The title field is the human-readable name for the insight and description is a description of what the insight does. In the most basic of insights, those 4 fields are all that is required to get going. In the case of this first insight, when it is enabled, the Ultralink code will examine every Ultralink on the page and find every one with a mylinktype link. It will then send one query for every mylinktype link found to message.js (which is residing inside the Ultralink Core) with the command name Trending and the corresponding link URL (we will get to what happens after the API result is returned later below).

{ name: 'TwoHopsAway', linkType: 'mylinktype', regex: '.*\/person\/.*', title: "Two Hops Away", description: "People that are two hops away from you." }
{
    name:        'TwoHopsAway',
    linkType:    'mylinktype',
    regex:       '.*\/person\/.*',
    title:       "Two Hops Away",
    description: "People that are two hops away from you."
}

This next example adds a regex field which reduces the set of links that API calls are made for down to only those mylinktype links which URLs match the specified regular expression. If you know your insight is only applicable to specific kinds of URLs then this can be used to pinpoint the exact set that your insight needs to examine.

{ name: 'HasAttributes', linkType: 'mylinktype', regex: '.*\/person\/.*', title: "Personal Attributes",
    description: "People that have the specified attributes that have specific attributes.", reportable: true, enableOverride: true,
    inputFields: [ { field: 'kind',  name: 'Kind',  type: 'selectBox', options: ['any', 'yes', 'no'], value: 'any', description: "This person is kind."  },
                   { field: 'smart', name: 'Smart', type: 'selectBox', options: ['any', 'yes', 'no'], value: 'any', description: "This person is smart." },
                   { field: 'funny', name: 'Funny', type: 'selectBox', options: ['any', 'yes', 'no'], value: 'any', description: "This person is funny." } ] }
{
    name:           'HasAttributes',
    linkType:       'mylinktype',
    regex:          '.*\/person\/.*',
    title:          "Personal Attributes",
    description:    "People that have the specified attributes that have specific attributes.",
    reportable:     true,
    enableOverride: true,
    inputFields:
    [
        {
            field:       'kind',
            name:        'Kind',
            type:        'selectBox',
            options:     ['any', 'yes', 'no'],
            value:       'any',
            description: "This person is kind."
        },
        {
            field:       'smart',
            name:        'Smart',
            type:        'selectBox',
            options:     ['any', 'yes', 'no'],
            value:       'any',
            description: "This person is smart."
        },
        {
            field:       'funny',
            name:        'Funny',
            type:        'selectBox',
            options:     ['any', 'yes', 'no'],
            value:       'any',
            description: "This person is funny."
        }
     ]
}

This insight has a special attribute called inputFields which describes the user input that needs to be provided for the insight handlers (further below) to determine if the insight has been triggered. inputFields is an array that contains objects that describe user input.

The field attribute is an identifier for the input so that the insight handler logic can identfy it. name is a human-friendly string for use when displaying UI related to the insight along with description. The type attribute indicates what kind of input control should be displayed for the input.

The different kinds of input field field values currrently supported are selectBox, numericCompare and *Search. In the example above, you can see that selectBox uses the options attribute to define an array of selections to populate a drop down box with. The value attribute indicates the default selection.

{ name: 'SpecificPerson', linkType: 'mylinktype', regex: '.*\/person\/.*', title: "Specific Person Lookup",
    description: "People who fit a specific profile according to number of investments, organization title and location.",
    inputFields: [ { field: 'num_investments', name: 'Investments', type: 'numericCompare', compare: '=', value: 'any', description: "Number of investments the person has made."   },
                   { field: 'title',           name: 'Role',        type: 'titleSearch'                                 description: "Role the person has held at an organization." },
                   { field: 'location',        name: 'Location',    type: 'locationSearch',                             description: "Current location the person is in."           } ] }

Here above we have another example that makes use of the numericCompare and *Search types. The numericCompare type produces a drop down box with <, >, <=, >=, = and != options. This drop down defaults to the value in the compare attribute. It also produces an input box where the user can enter numerical values. You might have noticed the any value popping up here and there in these examples. This is a special value which your insight handlers can generally take to mean that any value or result for this input should be valid (different insights may warrent a different interpretation though).

If you declare an input field with a type value that ends with the word Search then it simply produces an input field for the user to input a string (defaults to the any value). In the future there will be capabilities for more customizations based on the specific search type defined.

From these insight definitions, UI (in the browser extensions for instance) constructs an insights object that indicates all of the insights the user wants enabled along with any of their input (if applicable). It sends one query message for every hit Ultralink/link pair to message.js where it makes the appropriate API call to get the data needed for that insight and sends the results back where it is picked up by the appropriate insight handler.

Insight Handlers

if( (typeof Ultralink != "undefined") && 
    (typeof Ultralink.insightHandlers != "undefined") )
{
    // ... insight handlers ...
}

The insight handlers are the second major section inside insights.js. After making sure the environment it is runnning in is the correct one for insight handlers, it goes about defining them.

Ultralink.insightHandlers['TwoHopsAway'] = function( result )
{
    // ... logic to calculate distance based on the information in result ...

    if( distance == 2 ){ return "You are two hops away from this person on Service X."; }
};

Here we have a simple insight handler. All the information it needs to make the determination as to whether the Ultralink/link pair in question has triggered the insight, is contained in the result argument which is the result of the relevant API call made in message.js. If your insight code has examined the data and found the circumstances do indeed trigger the insight, then all you need to do is return a string that describes why this Ultralink triggered the insight. That string is combined any other strings that may have been produced by any other triggering insights and is used for the tooltip when the user hovers over the Ultralink. Because the insight was triggered, the Ultralink in question is highlighted in red to draw the user's attention.

Although that is the simplest kind of insight, user input, multiple instances of the same insight type and complex return data can make things more involved.

Ultralink.insightHandlers['SpecificPerson'] = function( result, insightType, insights, insightsOfType )
{
    var finalDescription = '';
    var finalData = [];

    for( var i = 0; i < insightsOfType.length; i++ )
    {
        var insight = insightsOfType[i];

        var iDescription = '';
        var iData = [];

        var passedAllFields = true;

        for( var ifc = 0; ifc < insight['inputFields'].length; ifc++ )
        {
            var inputField = insight['inputFields'][ifc];

            if( inputField['type'] == "numericCompare" )
            {
                if( inputField['value'].toLowerCase() != 'any' )
                {
                    var actualValue = 0;

                    switch( inputField['field'] )
                    {
                        case 'num_investments':
                        {
                            // ... calculate actualValue from result ...
                        } break;
                    }

                    if( !Ultralink.compareValues( inputField['compare'], actualValue, parseInt( inputField['value'] ) ) )
                    {
                        passedAllFields = false; break;
                    }

                    switch( inputField['field'] )
                    {
                        case 'num_investments':
                        {
                            var descriptionString = "This person has made " + inputField['compare'] + " " + testValue + " investments.";
                            iDescription = Ultralink.addToInsightDescription( descriptionString, iDescription );
                            iData.push({ 'field': inputField['field'] });
                        } break;
                    }
                }
            }
            else if( inputField['type'] == "locationSearch" )
            {
                if( inputField['location'].toLowerCase() != 'any' )
                {
                    var isSameLocation = false;
                    var matchedLocations    = "";
                    var lastMatchedLocation = "";

                    var inputLocation = inputField['location'].toLowerCase();

                    // ... calculate isSameLocation, matchedLocations and lastMatchedLocation from result ...

                    if( isSameLocation == true )
                    {
                        var descriptionString = "This person is located in " + matchedLocations + ".";
                        iDescription = Ultralink.addToInsightDescription( descriptionString, iDescription );
                        iData.push({ 'field': inputField['field'], 'location': lastMatchedLocation });
                    }
                    else{ passedAllFields = false; break; }
                }
            }
            else if( inputField['type'] == "titleSearch" )
            {
                // ... a similar process as above ...
            }
        }

        if( passedAllFields == true )
        {
            finalDescription = Ultralink.addToInsightDescription( iDescription, finalDescription );
            for( var i = 0; i < iData.length; i++ ){ finalData.push( iData[i] ); }
        }
    }

    if( finalData.length )
    {
        if( finalDescription == '' ){ finalDescription = "This person has a Service X link."; }
        return { 'description': finalDescription, 'data': finalData };
    }
};
Ultralink.insightHandlers['SpecificPerson'] =
function( result, insightType, insights, insightsOfType )
{
    var finalDesc = '';
    var finalData = [];

    for( var i = 0; i < insightsOfType.length; i++ )
    {
        var insight = insightsOfType[i];

        var iDesc = '';
        var iData = [];

        var passedAllFields = true;

        for( var ifc = 0; ifc < insight['inputFields'].length; ifc++ )
        {
            var inputField = insight['inputFields'][ifc];

            if( inputField['type'] == "numericCompare" )
            {
                if( inputField['value'].toLowerCase() != 'any' )
                {
                    var actualValue = 0;

                    switch( inputField['field'] )
                    {
                        case 'num_investments':
                        {
                            // ... calculate actualValue from result ...
                        } break;
                    }

                    var compare = inputField['compare'];
                    var inVal = parseInt( inputField['value'] );
                    if( !Ultralink.compareValues( compare, actualValue, inVal ) )
                    {
                        passedAllFields = false; break;
                    }

                    switch( inputField['field'] )
                    {
                        case 'num_investments':
                        {
                            var descString = "This person has made " +
                                              inputField['compare'] + " " +
                                              testValue + " investments.";
                            iDesc = Ultralink.addToInsightDescription( descString, iDesc );
                            iData.push({ 'field': inputField['field'] });
                        } break;
                    }
                }
            }
            else if( inputField['type'] == "locationSearch" )
            {
                if( inputField['location'].toLowerCase() != 'any' )
                {
                    var isSameLocation = false;
                    var matchedLocations    = "";
                    var lastMatchedLocation = "";

                    var inputLocation = inputField['location'].toLowerCase();

                    // ... calculate isSameLocation, matchedLocations
                    // and lastMatchedLocation from result ...

                    if( isSameLocation == true )
                    {
                        var descString = "This person is located in " +
                                         matchedLocations + ".";
                        iDesc = Ultralink.addToInsightDescription( descString, iDesc );
                        iData.push(
                        {
                            'field':    inputField['field'],
                            'location': lastMatchedLocation
                        });
                    }
                    else{ passedAllFields = false; break; }
                }
            }
            else if( inputField['type'] == "titleSearch" )
            {
                // ... a similar process as above ...
            }
        }

        if( passedAllFields == true )
        {
            finalDesc = Ultralink.addToInsightDescription( iDesc, finalDesc );
            for( var i = 0; i < iData.length; i++ ){ finalData.push( iData[i] ); }
        }
    }

    if( finalData.length )
    {
        if( finalDesc == '' ){ finalDesc = "This person has a Service X link."; }
        return { 'description': finalDesc, 'data': finalData };
    }
};
Ultralink.insightHandlers['SpecificPerson'] =
function( result, insightType, insights, insightsOfType )
{
    var finalDesc = '';
    var finalData = [];

    for( var i = 0; i < insightsOfType.length; i++ )
    {
        var insight = insightsOfType[i];

        var iDesc = '';
        var iData = [];

        var passedAllFields = true;

        var iFields = insight['inputFields'];

        for( var ifc = 0; ifc < iFields.length; ifc++ )
        {
            var iField = iFields[ifc];

            if( iField['type'] == "numericCompare" )
            {
                var iVal = iField['value'];

                if( iiVal.toLowerCase() != 'any' )
                {
                    var actual = 0;

                    switch( iField['field'] )
                    {
                        case 'num_investments':
                        {
                            // ... calculate actual value
                            // from result ...
                        } break;
                    }

                    var comp = iField['compare'];
                    var inV = parseInt( inputField['value'] );
                    if( !Ultralink.compareValues( comp, actual, inV ) )
                    {
                        passedAllFields = false; break;
                    }

                    switch( iField['field'] )
                    {
                        case 'num_investments':
                        {
                            var descString = "This person has made " + comp + " " + testValue + " investments.";
                            iDesc = Ultralink.addToInsightDescription( descString, iDesc );
                            iData.push({ 'field': iField['field'] });
                        } break;
                    }
                }
            }
            else if( iField['type'] == "locationSearch" )
            {
                var iLoc = iField['location'];

                if( iLoc.toLowerCase() != 'any' )
                {
                    var isSameLocation   = false;
                    var matchedLocations = "";
                    var lastMatched      = "";

                    var inputLoc = iLoc.toLowerCase();

                    // ... calculate isSameLocation,
                    // matchedLocations
                    // and lastMatched
                    // from result ...

                    if( isSameLocation == true )
                    {
                        var descString = "This person is located in " + matchedLocations + ".";
                        iDesc = Ultralink.addToInsightDescription( descString, iDesc );
                        iData.push(
                        {
                            'field':    iField['field'],
                            'location': lastMatched
                        });
                    }
                    else
                    {
                        passedAllFields = false;
                        break;
                    }
                }
            }
            else if( iField['type'] == "titleSearch" )
            {
                // ... a similar process as above ...
            }
        }

        if( passedAllFields == true )
        {
            finalDesc = Ultralink.addToInsightDescription( iDesc, finalDesc );
            for( var i = 0; i < iData.length; i++ )
            {
                finalData.push( iData[i] );
            }
        }
    }

    if( finalData.length )
    {
        if( finalDesc == '' )
        {
            finalDesc = "Person has a Service X link.";
        }
        return
        {
            'description': finalDesc,
            'data':        finalData
        };
    }
};

This insight handler has a lot to take in. Let's examine it piece-by-piece starting with the arguments passed into the handler. In addition to the data passed in through result it also passes the insightType and all the user specified insights (it does this in case you want to write one handler for more than one type of insight). Most of the time you will want to deal with insightsOfType which is an array of only the enabled insights of the current insight type.

Because some insights can support user input, users can specify multiple insights of the same type and so you should iterate through them all in insightsOfType. Because of that, you might have to coalesce all the results from every insight of that type together in a final result.

In the previous simple example, we simply returned a single string to indicate that the insight was triggered. In this more complex example, we return an object with a description field that serves the same purpose as when you simply return a string. Also, present in that object is the data field which can contain structured feedback you may want to pass to your view.js logic. There, you can use Ultralink.InlineView.matchesInsightData( name, field, test ) to check if a particular insight was triggered or not.

Ultralink.addToInsightDescription() is a convinience function that cleanly formats a result string depending on what an existing string's contents. Ultralink.compareValues() can be used with numericCompare input fields for quick numeric comparisons.

PreviousPrev
Next