/**
* Copyright © 2022 Benutech Inc. All rights reserved.
* http://www.benutech.com - help@benutech.com
* version: 1.21.0
* https://github.com/benutech-inc/ttb-sdk
* For latest release, please check - https://github.com/benutech-inc/ttb-sdk/releases
* */
(function () {
'use strict';
var methodsMapping, siteProtocol, defaults;
// methods and their API endpoints
methodsMapping = {
AUTH__LOGIN: {methodName: 'login', endpoint: 'webservices/login.json'},
AUTH__LOGIN_REMOTE: {methodName: 'loginRemote', endpoint: 'webservices/remote_login.json'},
AUTH__LOGIN_UUID: {methodName: 'loginUUID', endpoint: 'webservices/uuid_login.json'},
AUTH__LOGOUT: {methodName: 'logout', endpoint: 'webservices/logout.json'},
VENDOR__GET_PROFILE__USER: {methodName: 'TTB.getUserProfile', endpoint: 'webservices/get_vendor_user.json'},
VENDOR__GET_PROFILE__VENDOR: {methodName: 'TTB.getVendorProfile', endpoint: 'webservices/get_vendor/{{partnerKey}}.json'},
SPONSOR__GET_LIST: {methodName: 'TTB.getSponsors', endpoint: 'webservices/get_sponsors.json'},
SPONSOR__GET_SELECTION: {methodName: 'TTB.getSponsorSelection', endpoint: 'webservices/get_sponsor_selection.json'},
SPONSOR__SAVE_SELECTION: {methodName: 'saveSponsorSelection', endpoint: 'webservices/save_sponsor_selection.json'},
SPONSOR__CLEAR_SELECTION: {methodName: 'clearSponsorSelection', endpoint: 'webservices/clear_sponsor_selection.json'},
SPONSOR__ACCEPT_TOS: {methodName: 'TTB.getSponsors', endpoint: 'webservices/accept_tos/accept.json'},
SEARCH_PROPERTY__PARCEL: {methodName: 'searchByParcelNumber', endpoint: 'webservices/search_parcel_number.json'},
SEARCH_PROPERTY__ADDRESS: {methodName: 'searchBySiteAddress', endpoint: 'webservices/search_property/ttb.json'},
SEARCH_PROPERTY__OWNER: {methodName: 'searchByOwnerName', endpoint: 'webservices/search_owner_name/ttb.json'},
REPORT__ORDER: {methodName: 'orderReport', endpoint: 'webservices/order_report.json'},
REPORT__GET_TYPES: {methodName: 'getTypesReport', endpoint: 'webservices/types_report.json'},
PROPERTY__COMPS: {methodName: 'propertyComps', endpoint: 'webservices/property_comps.json'},
PROPERTY__DETAILS: {methodName: 'propertyDetails', endpoint: 'webservices/property_details.json'},
FARM__PE__CHECK_STATUS: {methodName: 'checkPEFarmStatus', endpoint: 'webservices/pe_farm_status/{{farmId}}.json'},
FARM__GET_DETAILS: {methodName: 'getFarmProperties', endpoint: 'webservices/get_farm/{{farmId}}.json'},
FARM__GET_LIST: {methodName: 'getFarmsList', endpoint: 'webservices/get_farm_metainfo.json'},
GLOBAL_SEARCH__PROPERTIES: {methodName: 'globalSearch', endpoint: 'webservices/global_search.json'},
GLOBAL_SEARCH__COUNT: {methodName: 'globalSearchCount', endpoint: 'webservices/global_search_count.json'},
GLOBAL_SEARCH__FIELDS: {methodName: 'getSearchFields', endpoint: 'webservices/get_search_fields.json'}
};
//siteProtocol = window.location.protocol;
siteProtocol = 'https:';
defaults = {
protocol: siteProtocol,
devPortSandbox: '9000',
devPortLanding: '9001',
devPortExport: '9002', // SERVE mode
// devPortExport: '9003', // PROD mode
exportAppBaseURL: 'https://ttb-export.herokuapp.com', // prod
// exportAppBaseURL: 'http://ttb-export-dev.herokuapp.com', // dev build
//partnerKey: '1-234-567-890', // no key by default.
sponsor: {
name: 'direct',
title: 'Benutech',
site: 'https://direct.titletoolbox.com/',
logoURL: 'https://s3-us-west-1.amazonaws.com/titletoolbox/company+logos/Benutech/Benute+Logo.png',
TOSURL: 'https://direct.api.titletoolbox.com/pages/tos/direct_tos'
},
enabledFeatures: 'iLookupWidget',
baseURLPattern: 'https://{{sponsorName}}.api.titletoolbox.com/',
scopedBootstrap: false,
classScopedBootstrap: 'scoped-bootstrap',
classScopedBootstrapHtml: 'scoped-bootstrap--html',
classScopedBootstrapBody: 'scoped-bootstrap--body',
debug: false,
sdkPrefix: 'ttb-sdk',
sessionKeyName: 'TTBSID',
sessionKeySkippedMethods: [
methodsMapping.AUTH__LOGIN.methodName, // e.g. 'login',
methodsMapping.AUTH__LOGIN_REMOTE.methodName,
methodsMapping.VENDOR__GET_PROFILE__USER.methodName,
methodsMapping.VENDOR__GET_PROFILE__VENDOR.methodName,
methodsMapping.SPONSOR__GET_SELECTION.methodName
],
autoFillAttr: 'data-ttb-field',
localStorageNames: {
connect__selectedSponsor: 'connect--selected-sponsor'
},
dataTableConfig: {
// CDNs: {
// CSS: 'https://ttb-export.herokuapp.com/libs/jquery-data-tables/jquery.dataTables.min.css',
// JS: 'https://ttb-export.herokuapp.com/libs/jquery-data-tables/jquery.dataTables.min.js'
// },
resources: {
CSS: '/libs/jquery-data-tables/jquery.dataTables.min.css',
JS: '/libs/jquery-data-tables/jquery.dataTables.min.js',
},
options: {}
},
iframeOptions: {
height: '600px'
},
modalOptions: {
sizeClass: 'modal-lg',
autoDestroy: true
},
// for all components
errorMessages: {
GENERAL__CONNECT_FAILED: 'Failed in connecting to TitleToolbox',
// CONNECT__NO_SPONSOR: 'No Partner - Please click "Connect" to select one.'
CONNECT__NO_SPONSOR: 'The data related features are supported by various partners. Please click Connect to select a partner.'
},
sponsorItemTemplate: [
'<tr>',
' <th scope="row">{{count}}</th>',
' <td>',
' <a href="{{websiteURL}}" target="_blank">',
' <img src="{{logoUrl}}" class="img-responsive" alt="Sponsor Logo" >',
' </a>',
' </td>',
' <td>',
' <strong>{{name}}</strong> <br>',
' {{websiteName}}',
' </td>',
' <td class="text-center">',
' {{sponsorActionButton}}',
' </td>',
'</tr>'
].join(''),
sponsorItemSelectedButton: [
'<button type="button" class="btn btn-success ttb-sdk--select-sponsor--selected" disabled>',
' <!-- <span class="ttb-sdk--common--icon ttb-sdk--common--icon-cross"></span> -->',
' <span class="ttb-sdk--common--icon ttb-sdk--common--icon-check"></span>',
' Selected',
'</button>'
].join(''),
sponsorItemSelectButton: [
'<button type="button" class="btn btn-primary"',
' data-sponsor-name="{{sponsorName}}"',
' data-sponsor-title="{{sponsorTitle}}"',
' data-sponsor-site="{{sponsorSite}}"',
' data-sponsor-logo="{{sponsorLogoURL}}"',
' data-sponsor-tos="{{sponsorTOSURL}}">',
' Select',
'</button>'
].join(''),
sponsorListTemplate: [
'<h3 class="ttb-sdk--select-sponsor--heading">{{resultsMessage}}</h3>',
'<div style="overflow-x: auto">',
'<table class="table ttb-sdk--selected-sponsor--list">',
' <thead>',
' <tr>',
' <th scope="col" width="80" class="text-center">#</th>',
' <th scope="col" width="80" class="text-center">Logo</th>',
' <th scope="col" width="80" class="text-center">Name</th>',
' <th scope="col" width="80" class="text-center"></th>',
' </tr>',
' <thead>',
' <tbody class="align-items-center">{{sponsorsMarkup}}</tbody>',
'</table>',
'</div>'
].join(''),
modalTemplate: [
'<div id={{modalId}} class="ttb-sdk--modal modal" tabindex="-1" role="dialog" aria-labelledby="{{modalId}}-label" aria-hidden="true">',
' <div class="modal-dialog {{sizeClass}}" role="document">',
' <div class="modal-content">',
' <div class="modal-header">',
' <button type="button" class="close" data-dismiss="modal" aria-label="Close" title="Close">',
' <span aria-hidden="true">×</span>',
' </button>',
' <h4 class="modal-title" id="{{modalId}}-label">{{title}}</h4>',
' </div>',
' <div class="modal-body">',
' {{bodyContent}}',
' </div>',
' </div>',
' </div>',
'</div>'
].join('')
};
/**
* The main class for consuming TTB web services.
* @class
* @alias TTB
*
* @classdesc <p class="main-desc">JavaScript SDK for consuming webservices and widgets by TitleToolBox from third-party websites.</p>
*
* <p><strong id="dependencies">Dependencies:</strong></p>
* <p>
* <strong>JQuery</strong> - version <code>1.9.x</code> or higher should work. We recommend the latest version <code>3.x</code> <br/>
* <code> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> </code>
* </p>
*
* <p>
* <strong>Bootstrap</strong> - For modals, and rendering widgets, SDK uses bootstrap UI and script. <br/>
* <code> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> </code><br>
* Official CSS: <br>
* <code> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> </code> <br/>
* Scoped Bootstrap version: <br>
* Having non-bootstrap based site ? please use the following scoped-bootstrap version to limit bootstrap styles to SDK widgets only. (bootstrap v3.3.7 used.)<br>
* <code> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/benutech-inc/ttb-sdk@1.21.0/dist/scoped-bootstrap.min.css"> </code>
* </p>
*
* <p>
* <strong>Google Maps</strong> - Optional/for some methods only - E.g. ttb.instantLookupWidget(), and other google related methods/widgets only.)
* <code> <script src="https://maps.googleapis.com/maps/api/js?key=GOOGLE_API_KEY&libraries=places&callback=googleInit"></script> </code><br/>
* (For API KEY, <a target="_blank" href="https://support.google.com/googleapi/answer/6158862">Google documentation</a> will be helpful.
* </p>
*
* <p>
* <strong>TitleToolBox SDK </strong> files (1 script, and 1 style), can be pulled via our public repo link:
* <i>(keep the [latest version]{@link https://github.com/benutech-inc/ttb-sdk/releases})</i><br>
* <code> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/benutech-inc/ttb-sdk@1.21.0/dist/ttbSdk.min.css"> </code>
* <code> <script src="https://cdn.jsdelivr.net/gh/benutech-inc/ttb-sdk@1.21.0/dist/ttbSdk.min.js"></script> </code>
* <br><br>OR via<strong> Bower </strong> using <code>bower install ttb-sdk --save</code>
* <br><br>
*
* <i style="font-size: 13px;">SDK's <strong>NPM</strong> package will be released soon...</i>
* </p>
*
* @param {Object} config - The configuration info required
*
* @param {String} config.partnerKey - The Partner-Key is a unique ID assigned to you by Company, that to be sent on
* every request from your site.
*
* @param {String} [config.baseURL="https://direct.api.titletoolbox.com/"] - The Base URL to be used for APIs calls. (note - we can alternatively use <code>sponsor</code>
* and/or <code>baseURLPattern</code> to keep switching to custom <code>baseURL</code> on the fly.)
*
* @param {Object} [config.sponsor] - The Title Company Sponsor object to be used in generating baseURL. contains name, title, site, logoURL, and TOSURL.
* (Note - It will be ignored if baseURL is already passed.)
*
* @param {String} [config.baseURLPattern="https://{{sponsorName}}.api.titletoolbox.com/"] - The URL pattern to be used
* to generate the baseURL which includes the <code>sponsor.name</code> provided. Must contain {{sponsorName}} at least once.
* (Note - It will be ignored if <code>baseURL</code> is already passed.)
*
* @param {Function} [config.onSessionExpireV3] - [Recommended over deprecated onSessionExpireV2()] - The callback / handler to be called
* whenever an API receives <code>401 - UNAUTHENTICATED</code> (session expired) from server, to renew the session on the fly and resume with
* widgets or API methods calls internally.
* This can be used to get a new valid "stk" (or "uuid" for loginUUID() case) from vendor site, via an ajax request to vendor server,
* or via in-memory store on FE, to renew user login session from TTB server.
* Method should return a promise, which should be resolved with an "authConfig" object as
* {authMethod: "loginRemote", // or "loginUUID", payload: loginRemotePayload // or {uuid: "xxxxxx"} }.
* (For payload details, check loginRemote() method or loginUUID() method whichever is involved for the target vendor authentication. )
* @param {Object} config.onSessionExpireV3.info - Similar to deprecated <code>onSessionExpireV2</code>'s "info" parameter.
*
* @param {String} [config.autoFillAttr="data-ttb-field"] - The attribute to be used for auto-fill input fields when
* <code>options.autoFillContext</code> specified in methods which support auto-fill.
* (Note: the attribute value on those inputs would be used to evaluate to <code>res.response.data</code>
* - For example: <input type="text" <code>data-ttb-field="GeneralInfo.Bedrooms"</code> />
* or <input type="text" <code>data-ttb-field="GeneralInfo['Year Built']"</code> />
*
* @param {String} [config.scopedBootstrap=false] - Whether the scoped bootstrap version is used.
* (recommended when non-bootstrap sites faces styles conflicts with official bootstrap CSS)
*
* @param {String} [config.debug=true] - SDK debug mode flag useful for logs, etc.
*
* @param {Function} [config.onSessionExpireV2] - (DEPRECATED! Please check onSessionExpireV2() ) - The callback / handler to be called
* whenever an API receives <code>401 - UNAUTHENTICATED</code> (session expired) from server, to renew the session on the fly and resume with
* widgets or API methods calls internally.
* This can be used to get a new valid "stk" from vendor site, via an ajax request to vendor server,
* or via in-memory store on FE, to renew user login session from TTB server.
* Method should return a promise, which should be resolved with a loginRemotePayload object. (Check loginRemotePayload details from loginRemote() method.)
* @param {Object} config.onSessionExpireV2.info - Contains details against the failed request. This can be used for advanced handling. <br>
* <code>info</code> object will contain <code>methodName</code>, <code>endpoint</code>, <code>requestConfig</code>, and <code>requestError</code>.
*
* @return {Object} ttb - The instance associated with the provided configuration.
*
* @example
* // With basic and minimum requirement.
* var ttb = new TTB({
* partnerKey: 'xxxxxxxxxxxxxxx',
* });
*
* @example
* // With advanced configuration for custom baseURL, and logs for debug mode.
* var ttb = new TTB({
* partnerKey: 'xxxxxxxxxxxxxxx',
* baseURL: 'https://direct.api.titletoolbox.com/',
* debug: true
* });
*
* @example
* // With advanced configuration for custom <code>baseURLPattern</code> and <code>sponsor</code>, and custom auto-fill attributes.
* var ttb = new TTB({
* partnerKey: 'xxxxxxxxxxxxxxx',
* baseURLPattern: 'https://customdomain.com/api/{{sponsorName}}',
* sponsor: {...} // switchable later via ttb.setSponsor(),
* autoFillAttr: 'data-model',
* scopedBootstrap: true
* });
*
* @example
* // (Optional but Recommended !) (for auth method: remoteLogin()) - registering session-expired (401 status error) handler for ttb ajax requests.
* // This example is for those vendors who use loginRemote() method for authentication.
* // live working example will be provided soon.
*
* // your app store / constant having configuration
* var ttbStore = {
* ttb: undefined,
* partnerKey: '558b3e66-47b2-477d-a0d3-6d85db4c3148',
* stk: '64on7i137ksveoa18os6i7g9a3', // assuming you maintain user valid stk in your store.
* getuser_url: 'https://newlawyersie.api.titletoolbox.com/webservices/get_ttb_user.json',
* };
*
* // step#2 instantiate the TTB with your (vendor's) credentials. check full config from TTB instance section.
* ttbStore.ttb = new TTB({
* partnerKey: ttbStore.partnerKey,
* onSessionExpireV3: ttbOnSessionExpireV3 // note suffix "V3" in "onSessionExpireV3"
* });
*
* // step#3 set up a sessionExpire handler. following is just an example.
* // this callback gets called, whenever any ttb method Ajax call encounters 401.
* function ttbOnSessionExpireV3(info) {
* console.log('ttbOnSessionExpireV3: No / Expired Session.', info); // check out "info" in console for any advanced handling.
*
* // step#3.1 build required info by getting the latest / valid stk (depends on vendor site's login system.)
* // option 1 - Some vendors have it in store on Frontend.
* // option 2 - Some vendors get latest stk from their server via ajax call.
* // option 3 - Some vendors perform a login prompt modal to renew user session on their login system to get latest stk for TTB.
*
* // for example vendor has option 2 (or even option 1) case.
* // build required info
* var authConfig = {
* authMethod: 'loginRemote',
* payload: {
* stk: ttbStore.stk,
* getuser_url: ttbStore.getuser_url
* }
* };
*
* // step#3.2 Simply return the resolved promise containing this info, leave the rest on SDK to perform TTB system login, and retry the failed requests, like nothing happened. ^ _ ^
* return TTB.utilPromiseResolve(authConfig);
* }
*
* @example
* // (Alternate) (for auth method: remoteUUID()) - registering session-expired (401 status error) handler for ttb ajax requests.
* // This example is for those vendors who use loginUUID() method for authentication.
* // live working example will be provided soon.
*
* // your app store / constant having configuration
* var ttbStore = {
* ttb: undefined,
* partnerKey: '558b3e66-47b2-477d-a0d3-6d85db4c3148',
* uuid: '123b3e66-47b2-477d-a0d3-6d85db4c3456',
* };
*
* // step#2 instantiate the TTB with your (vendor's) credentials. check full config from TTB instance section.
* ttbStore.ttb = new TTB({
* partnerKey: ttbStore.partnerKey,
* onSessionExpireV3: ttbOnSessionExpireV3 // note suffix "V3" in "onSessionExpireV3"
* });
*
* // step#3 set up a sessionExpire handler. following is just an example.
* // this callback gets called, whenever any ttb method Ajax call encounters 401.
* function ttbOnSessionExpireV3(info) {
* console.log('ttbOnSessionExpireV3: No / Expired Session.', info); // check out "info" in console for any advanced handling.
*
* // step#3.1 build required info by getting the latest / valid UUID (depends on vendor site's login system.)
* // option 1 - Some vendors have it in store on Frontend.
* // option 2 - Some vendors get latest UUID from their server via ajax call.
* // option 3 - Some vendors perform a login prompt modal to renew user session on their login system to get latest UUID for TTB.
*
* // for example vendor has option 2 (or even option 1) case.
* // build required info
* var authConfig = {
* authMethod: 'loginUUID',
* payload: {
* uuid: ttbStore.uuid,
* }
* };
*
* // step#3.2 Simply return the resolved promise containing this info, leave the rest on SDK to perform TTB system login, and retry the failed requests, like nothing happened. ^ _ ^
* return TTB.utilPromiseResolve(authConfig);
* }
*
* // That's it! We are ready to shine!
*
* */
window.TTB = function (config) {
/**
* @type {Object}
* @desc The configuration passed while instantiating main SDK class.
* For details, Please check documentation of <code> new TTB(...) </code> constructor.
* */
this.config = config;
// setup instance id for recognising relevant logs of specific instance.
window.TTB.instancesCount = window.TTB.instancesCount || 0;
window.TTB.instancesCount++;
/**
* @type {Number}
* @desc Instance Id to recognise instances and other usages. For example, Tracing relevant logs.
* */
this.instanceId = window.TTB.instancesCount;
// setup default baseURL
this.baseURLPattern = config.baseURLPattern || defaults.baseURLPattern;
// set sponsor via proper channel. (priority => config > previously selected > default)
var sponsor = config.sponsor || window.TTB._getLocal(defaults.localStorageNames.connect__selectedSponsor) || window.TTB.utilCopy(defaults.sponsor);
var baseURL = this.setSponsor(sponsor);
this.baseURL = config.baseURL || baseURL;
this.autoFillAttr = config.autoFillAttr || defaults.autoFillAttr;
this.scopedBootstrap = config.scopedBootstrap || defaults.scopedBootstrap;
this.debug = config.debug || defaults.debug;
/* exporting flags to main class for static methods */
// scoped bootstrap to be true if at least one instance contains the flag.
window.TTB.scopedBootstrap = !window.TTB.scopedBootstrap ? this.scopedBootstrap : window.TTB.scopedBootstrap;
// namespace to hold any third-party instances references for later usage like destroy. e.g. dataTable
//this._refs = {
// instantLookup: {}
//};
this._log(['TTB SDK instantiated. | version: ', window.TTB.version]);
};
/**
* @memberof TTB
* @alias version
* @static
* @description The version of the SDK being used.
* @type String
* */
window.TTB.version = '1.21.0';
/**
* @memberof TTB
* @alias version
* @static
* @description Total counts of TTB instances created.
* @type String
* */
window.TTB.instancesCount = 0;
/**
* @memberof TTB
* @alias debug
* @static
* @description The debug logs flag for static functions only.
* (For instance methods, The "debug" property passed while instantiating, will be used.)
* @type {Boolean}
* */
window.TTB.debug = false;
/**
* @memberof TTB
* @alias _log
* @static
* @private
* @description Logger for static functions only. uses window.TTB.debug flag to enable/disable logging.
*
* @param {Array} args - list of values to be logged.
* */
window.TTB._log = function (args) {
window.TTB.debug && console.log.apply(console, [defaults.sdkPrefix + ' : static :'].concat(args));
};
/**
* @memberof TTB
* @alias _createDefaultInstance
* @static
*
* This static method provides an instance for the internal use for TTB static methods.
* @private
*
* @param {String} partnerKey - The partner key provided by support team for the consumer site.
* @param {Object} sponsor - The sponsor object contains name, title, and TOSURL.
*
* @example
*
* var defaultTtb = TTB._createDefaultInstance();
*
* @return {Object} instance - instance object created with default configuration.
*
* */
window.TTB._createDefaultInstance = function (partnerKey, sponsor) {
return new TTB({
partnerKey: partnerKey || defaults.partnerKey,
sponsor: sponsor
});
};
/**
* @memberof TTB
* @alias _getLocal
* @static
*
* This static method gets a model from local storage with given ttb sdk prefix.
* @private
*
* @param {String} key - name of the model against which was the value saved.
*
* @example
*
* var actionName = TTB._getLocal('selectedAction');
*
* @return {any} value - value retrieved from local Storage.
*
* */
window.TTB._getLocal = function (key) {
try {
var value = window.localStorage.getItem(defaults.sdkPrefix + '--' + key);
switch (value) {
case 'null':
return null;
case 'undefined':
return undefined;
default:
return (value[0] === '{' || value[0] === '[') ? JSON.parse(value) : value;
}
} catch (e) {
return null;
}
};
/**
* @memberof TTB
* @alias _setLocal
* @static
*
* This static method gets a model from local storage with given ttb sdk prefix.
* @private
*
* @param {String} key - name of the model against which was the value saved.
* @param {any} value - the value to be saved.
*
* @example
*
* var actionName = TTB._setLocal('selectedAction', 'fullProfileReport');
*
* @return {Boolean} successWrite - if was successful write. helpful for browsers having "site data" write disabled.
*
* */
window.TTB._setLocal = function (key, value) {
try {
var valueToWrite = typeof value === 'object' ? JSON.stringify(value) : value;
window.localStorage.setItem(defaults.sdkPrefix + '--' + key, valueToWrite);
return true;
} catch (e) {
return false;
}
};
/**
* @memberof TTB
* @alias _modal
* @static
*
* Shows a modal with given dynamic content.
* @private
*
* @param {Object} options - configuration options for the modal.
* @param {String} options.title - The Title of the modal to be shown inside the modal header - can be plain text or HTML markup.
* @param {String} options.bodyContent - The body content - can be plain text or HTML markup.
* @param {String} [options.id="Dynamically generated number e.g. ttb-sdk--1234567890"] - A unique id to be assigned to the modal
* @param {String} [options.sizeClass="modal-sm"] - modal dialog size class e.g. modal-lg, modal-md, modal-sm and custom as modal-full
* @param {Boolean} [options.autoDestroy="true"] - To auto destroy the modal from the DOM, after it gets closed.
*
* @param {Function} [options.onBeforeShow] - A callback function to be invoked when modal is about to be shown. it uses <code>show.bs.modal</code> bootstrap modal event.
* @param {Function} [options.onShown] - A callback function to be invoked when modal has been triggered and shown to user. it uses <code>shown.bs.modal</code> bootstrap modal event.
* @param {Function} [options.onClose] - A callback function to be invoked when modal has been closed by the user. it uses <code>hidden.bs.modal</code> bootstrap modal event.
* @param {Function} [options.onBeforeClose] - A callback function to be invoked when modal is about to be close. it uses <code>hide.bs.modal</code> bootstrap modal event.
*
* @return {String} $modal - A JQuery reference to the modal DOMNode Element.
*
* */
window.TTB._modal = function (options) {
var $modal, modalTemplate;
options.id = options.id || (defaults.sdkPrefix + '--' + Date.now());
options.sizeClass = options.sizeClass || defaults.modalOptions.sizeClass;
options.autoDestroy = options.autoDestroy != undefined ? options.autoDestroy : defaults.modalOptions.autoDestroy;
// generate the modal template against given info
modalTemplate = defaults.modalTemplate
.replace(/\{\{modalId}}/g, options.id)
.replace('{{sizeClass}}', options.sizeClass)
.replace('{{title}}', options.title)
.replace('{{bodyContent}}', options.bodyContent);
// inject the modal inside the DOM
//return $(document.body).append(modalTemplate);
$modal = $(modalTemplate).appendTo(document.body);
//options.onBeforeShow && $modal.on('show.bs.modal', options.onBeforeShow);
options.onShown && $modal.on('shown.bs.modal', options.onShown);
//options.onBeforeClose && $modal.on('hide.bs.modal', options.onBeforeClose);
//options.onClose && $modal.on('hide.bs.modal', options.onClose);
$modal.on('show.bs.modal', onBeforeShowModal);
$modal.on('hide.bs.modal', onBeforeCloseModal);
// to auto destroy, always listen to close event
if (options.autoDestroy) {
$modal.on('hidden.bs.modal', function () {
// invoked the given callback, before destroying
options.onClose && options.onClose();
// destroy the modal from DOM.
$modal.remove();
});
} else {
options.onClose && $modal.on('hide.bs.modal', options.onClose);
}
return $modal;
// to be invoked before showing the modal. Adds bootstrap classes
function onBeforeShowModal() {
// remove scoped bootstrap related classes
if (window.TTB.scopedBootstrap) {
$('html').addClass(defaults.classScopedBootstrapHtml);
$('body').addClass(defaults.classScopedBootstrapBody);
}
// invoked the given callback
options.onBeforeShow && options.onBeforeShow();
}
// to be invoked before closing the modal. Removes bootstrap classes
function onBeforeCloseModal() {
// remove scoped bootstrap related classes
if (window.TTB.scopedBootstrap) {
$('html').removeClass(defaults.classScopedBootstrapHtml);
$('body').removeClass(defaults.classScopedBootstrapBody);
}
// invoked the given callback
options.onBeforeClose && options.onBeforeClose();
}
};
/**
* @memberof TTB
* @alias utilIframeModal
* @static
*
* Shows a modal having an iframe with given information, loaded. provide a subscription to "message" event of window, listening that iframe site origin.
* @private
*
* @param {Object} modalOptions - configuration options for the modal. Please check TTB._modal for parameters information.
*
* @param {Object} iframeOptions - configuration options for the iframe site. (which is going to be loaded into the iframe)
* @param {String} iframeOptions.id - The "id" value for the iframe element.
* @param {String} iframeOptions.height - The "height" value for the iframe element.
* @param {String} iframeOptions.origin - The origin of the site. E.g. "https://www.example.com/"
* @param {String} iframeOptions.pathname - The pathname of the site. E.g. "/index.html" The final "src" value will be generated using origin, pathname, and params.
* @param {Object} [iframeOptions.params] - params object to be auto transformed into query params with the final src URL. (hostOrigin is already being added)
* @param {Function} [iframeOptions.onMessage] - A callback to be invoked when a message event is broadcasted from the iframe site origin.
* parameters will be "data" - which is passed from iframe site, and "event" - complete event object.
*
* @return {String} $modal - A JQuery reference to the modal DOMNode Element.
*
* */
window.TTB.utilIframeModal = function (modalOptions, iframeOptions) {
var $modal, o;
iframeOptions.height = iframeOptions.height || defaults.iframeOptions.height;
o = {};
o.src = iframeOptions.origin + iframeOptions.pathname + '?hostOrigin={{hostOrigin}}'
.replace('{{hostOrigin}}', window.location.origin);
// check if additional params are passed
if (iframeOptions.params) {
// technique used to pass URLs. https://stackoverflow.com/a/1739132
$.each(iframeOptions.params, function(key, value) {
o.src += value ? ('&' + key + '=' + encodeURIComponent(value)) : '';
});
}
o.iframeTemplate = [
'<iframe src="{{src}}" id="{{iframeId}}" name="{{iframeId}}" width="100%" height="{{height}}"></iframe>'
].join('')
.replace('{{src}}', o.src)
.replace('{{height}}', iframeOptions.height)
.replace(/\{\{iframeId}}/g, iframeOptions.id);
// subscribe to message event from iframe site, only when onMessage is provided
if (iframeOptions.onMessage) {
window.addEventListener('message', receiveMessage, false);
o.modalOptionsOnClose = modalOptions.onClose;
modalOptions.onClose = function () {
window.TTB._log(['utilIframeModal: onClose']);
// invoked any onClose callback if it was provide via modalOptions.
o.modalOptionsOnClose && o.modalOptionsOnClose();
// unsubscribe the message event when modal has been closed.
window.removeEventListener('message', receiveMessage);
};
}
// take in existing bodyContent if given.
modalOptions.bodyContent = [modalOptions.bodyContent || '', o.iframeTemplate].join('');
$modal = window.TTB._modal(modalOptions);
// triggering .modal() of bootstrap
$modal.modal({
//backdrop: 'static'
});
return $modal;
// listener to window "message" event.
function receiveMessage(event) {
window.TTB._log(['utilIframeModal: receiveMessage: origin:', event.origin]);
if (event.origin !== iframeOptions.origin) {
//window.TTB._log(['utilIframeModal: receiveMessage: from other origin:', event.origin]);
return;
}
window.TTB._log(['utilIframeModal: receiveMessage: event: ', event]);
iframeOptions.onMessage(event.data, event);
}
};
/**
* @memberof TTB
* @alias utilGenerateUniqueId
* @static
*
* Generates a unique string Id for list records.
* @private
*
* @param {String} [prefix] - a prefix string to include to generated unique id.
* @param {String} [suffix] - a suffix string to include to generated unique id.
*
* @return {String} UniqueId - A possibly unique id helpful for records identity.
*
* */
window.TTB.utilGenerateUniqueId = function (prefix, suffix) {
return defaults.sdkPrefix + '--' + (prefix || '') + Date.now() + '-' + parseInt(Math.random() * 100000) + (suffix || '');
};
/**
* @memberof TTB
* @alias utilCapitalize
* @static
*
* Capitalizes the given string.
* @private
*
* @param {String} [input] - string to transform into capital case
*
* @return {String} value - capitalized string.
*
* */
window.TTB.utilCapitalize = function(input) {
var capWord = function (wrd) {
return wrd.charAt(0).toUpperCase() + wrd.substr(1).toLowerCase();
};
input += '';
return input.split(' ').map(capWord).join(' ');
};
/**
* @memberof TTB
* @alias getTTBExportAppBaseURL
* @static
*
* Generates baseURL of the ttb-export app, based on the environment. i.e. local development vs production.
* @private
*
* @return {String} ttbExportAppBaseURL - the baseURL of the ttb-export app.
*
* */
window.TTB.getTTBExportAppBaseURL = function() {
// for testing only - serve PROD url to test locally.
// return defaults.exportAppBaseURL;
// check environment to serve relevant base url
return [defaults.devPortSandbox, defaults.devPortLanding].indexOf(window.location.port) >= 0
? ('http://localhost:' + defaults.devPortExport) : defaults.exportAppBaseURL;
}
/**
* @memberof TTB
* @alias utilLoadDataTable
* @static
*
* Injects assets (JS, CSS) for dataTable
* @private
*
* @param {Function} [onLoad] - callback to be invoked when done loading data table.
*
* @return {String} value - capitalized string.
*
* */
window.TTB.utilLoadDataTable = function(onLoad) {
var baseURL, cssFilePath, jsFilePath;
// check if lib has already been async loaded - initialize right away
if (!!$(document).DataTable) {
window.TTB._log(['utilLoadDataTable: data-table: already available: init']);
onLoad && onLoad();
} else {
window.TTB._log(['utilLoadDataTable: data-table: not available yet: loading files: CSS added.']);
// destination - dynamic - dev vs prod.
baseURL = window.TTB.getTTBExportAppBaseURL();
cssFilePath = baseURL + defaults.dataTableConfig.resources.CSS;
jsFilePath = baseURL + defaults.dataTableConfig.resources.JS;
// load styles
$(document.head)
.append('<link type="text/css" rel="stylesheet" href="'+ cssFilePath + '"/>');
// load script
$.ajax({
method: 'GET',
url: jsFilePath,
dataType: 'script',
cache: true,
success: function (content) {
window.TTB._log(['utilLoadDataTable: data-table: inject JS: success:']);
// initialize after browser is done with including script
onLoad && setTimeout(onLoad, 0);
},
error: function (reason) {
window.TTB._log(['utilLoadDataTable: data-table: inject JS: failure: ', reason]);
}
});
}
};
/**
* @memberof TTB
* @alias utilRenderTable
* @static
*
* Generate and render a table based on given data and options.
* @private
*
* @param {Array<Object>} records - the list of objects / records to show table against.
*
* @param {Object} options - configuration options for the table list. (which is going to be loaded into the given destination)
* @param {String} options.containerSelector - The selector value for the target container.
* @param {Array<Object>} options.columns - list of columns, each having "label", and "fieldName" fields.
* @param {Function} options.onInit - callback to be called when data-table lib is injected and instance has been created.
* @param {Function} options.onSelect - callback to be called when user selects any record from the list.
*
* @return {String} $container - A JQuery reference to the container DOMNode Element.
*
* */
window.TTB.utilRenderTable = function (records, options) {
var $container, o;
//options.hover = options.hover || defaults.tableOptions.height;
o = {
selectedRecordIndex: undefined,
dataTableInstance: undefined,
classSelected: 'ttb-sdk--table--record-selected',
tableId: undefined,
headings: undefined,
list: undefined,
tableTemplate: undefined,
tableMarkup: undefined
};
o.tableId = window.TTB.utilGenerateUniqueId('table-');
o.tableTemplate = [
'<div class="table-responsive" id="{{tableId}}">',
//' <table class="table display table-hover table-striped table-condensed table-bordered">',
' <table class="display">',
' <thead>',
' {{headings}}',
' </thead>',
' <tbody>',
' {{list}}',
' </tbody>',
' </table>',
'</div>'
].join('');
// capture the reference to container and for now hide the container
$container = $(options.containerSelector)
.hide();
// generate headings
o.headings = [
'<tr>',
// select button against each record
'<td></td>',
// all columns values
options.columns.map(function(column) {
return '<th>{{label}}</th>'.replace('{{label}}', column.label);
}).join(''),
'</tr>'
];
// generate list / records
o.list = records.map(function(record, recordIndex) {
return [
'<tr data-record-index="'+ recordIndex +'">',
// select button against each record
'<td><button class="btn btn-primary ttb-sdk--instant-lookup--select">Select</button></td>',
// all columns values
options.columns.map(function(column) {
return '<td>{{value}}</td>'.replace('{{value}}', record[column.fieldName] || '-');
}).join(''),
'</tr>'
].join('');
});
o.tableMarkup = o.tableTemplate
.replace('{{tableId}}', o.tableId)
.replace('{{headings}}', o.headings.join(''))
.replace('{{list}}', o.list.join(''));
// render the generated table markup
$container.empty().append(o.tableMarkup);
// listen to select button to send the selection info to host function
$container.find('table').on('click', '.ttb-sdk--instant-lookup--select', onRecordSelect);
// load data-table library in advance.
window.TTB.utilLoadDataTable(initializeDataTable);
/* functions declarations below */
// initialize data-table after checking with loading assets.
function initializeDataTable() {
window.TTB._log(['utilRenderTable: initializeDataTable: init']);
o.dataTableInstance = $container.find('table').DataTable();
o.dataTableInstance.on('draw.dt', activateSelectRecord);
// show the rendered table
$container.show();
// invoked passed onInit callback to forward the control.
options.onInit(o.dataTableInstance);
}
// to be invoked when a record is selected.
function onRecordSelect(event) {
var $record, record;
//window.TTB._log(['utilRenderTable: onRecordSelect:']);
// capture target record row.
$record = $(this).closest('tr');
o.selectedRecordIndex = +$record.attr('data-record-index');
activateSelectRecord();
record = records[o.selectedRecordIndex];
window.TTB._log(['utilRenderTable: onRecordSelect: select: record:', o.selectedRecordIndex, record]);
options.onSelect(record);
}
// to be invoked when search, or pagination re-draws the list.
//function onTableReDraw() {
// window.TTB._log(['utilRenderTable: onTableReDraw']);
//
//}
// add a class of selected entry and remove prev selection.
function activateSelectRecord() {
window.TTB._log(['utilRenderTable: activateSelectRecord:']);
$container.find('table tr')
.removeClass(o.classSelected)
.filter('[data-record-index="' + o.selectedRecordIndex + '"]')
.addClass(o.classSelected);
}
return $container;
};
/**
* @memberof TTB
* @alias utilBuildSponsorInfo
* @static
* @description Build up the sponsor object for setting new sponsor on TTB instance via setSponsor().
*
* @param {Object} sponsorMeta - sponsor meta object retrieved from response of getSponsors().
*
* @return {Object} sponsorInfo - object contains name, title, site, etc.
* */
window.TTB.utilBuildSponsorInfo = function (sponsorMeta) {
return {
name: sponsorMeta.vertical_name,
title: sponsorMeta.company_info.company_name,
site: sponsorMeta.site_url,
logoURL: sponsorMeta.company_info.logo_url,
TOSURL: sponsorMeta.TOS_content
};
};
/**
* @memberof TTB
* @alias utilBuildFullAddress
* @static
* @description Builds up the full address using various address fields, usually helpful to build full address for
* target property to open it's net sheet. <br>
* It uses this pattern: [h#] [streetName] [suf], [city], [state] [zip] to build address as
* e.g. "1234 Collins Ave, Miami Beach, FL 33140"
*
* @param {Object} property - result record retrieved via either from global search, search by address/owner/APN, nearby search, etc.
*
* @return {String} propertyFullAddress - complete address string built using each available field.
* */
window.TTB.utilBuildFullAddress = function (property) {
// e.g. 1234 Collins Ave, Miami Beach, FL 33140
// pattern: [h#] [streetName] [suf], [city], [state] [zip]
return (property.sa_site_house_nbr ? (property.sa_site_house_nbr + ' ') : '') + // 1234
(property.sa_site_street_name ? (property.sa_site_street_name + ' ') : '') + // Collins
(property.sa_site_suf ? (property.sa_site_suf + ', ') : '') + // Ave,
(property.sa_site_city ? (property.sa_site_city + ', ') : '') + // Miami Beach,
(property.sa_site_state ? (property.sa_site_state + ' ') : '') + // FL
(property.sa_site_zip ? (property.sa_site_zip) : ''); // 33140
};
/**
* @memberof TTB
* @alias getUserProfile
* @static
* @private
*
* @description
* This static method gets the user profile against the given "stk" and "getuser_url".
*
* @param {Object} payload - The payload object containing required info
* @param {String} payload.stk - See more about "stk" from <code>loginRemote()</code>
* @param {String} payload.getuser_url - See more about "getuser_url" from <code>loginRemote()</code>
*
* @param {String} partnerKey - The partner key provided by support team for the consumer site.
*
* @example
*
* var payload = {
* stk: "xxxxxxxxxxxxxxx"
* };
*
* var partnerKey = 'xxxxxxxxxxxxxxxx';
*
* TTB.getUserProfile(payload, partnerKey)
* .then(function(res) {
* if (res.response.status === 'OK') {
* // your success code here to consume res.response.data
* console.log(res.response.data);
* } else {
* // your failure code here to consume res.response.data
* console.log(res.response.data);
* }
* }, function(err) {
* // your failure code here
* })
* .always(function() {
* // your on-complete code here as common for both success and failure
* });
*
* @return {Object} promise - Jquery AJAX deferred promise is returned which on-success returns the required info.
*
* */
window.TTB.getUserProfile = function (payload, partnerKey) {
// get a default instance for internal use
var ttb = window.TTB._createDefaultInstance(partnerKey);
var request = {
method: 'POST',
data: JSON.stringify(payload),
xhrFields: {
withCredentials: false
}
};
return ttb._ajax(request, methodsMapping.VENDOR__GET_PROFILE__USER);
};
/**
* @memberof TTB
* @alias getVendorProfile
* @static
* @private
*
* @description
* This static method gets the vendor profile against the given "partnerKey".
*
* @param {String} partnerKey - The partner key provided by support team for the consumer site.
*
* @example
*
* var partnerKey = 'xxxxxxxxxxxxxxxxx';
*
* TTB.getVendorProfile(partnerKey)
* .then(function(res) {
* if (res.response.status === 'OK') {
* // your success code here to consume res.response.data
* console.log(res.response.data);
* } else {
* // your failure code here to consume res.response.data
* console.log(res.response.data);
* }
* }, function(err) {
* // your failure code here
* })
* .always(function() {
* // your on-complete code here as common for both success and failure
* });
*
* @return {Object} promise - Jquery AJAX deferred promise is returned which on-success returns the required info.
*
* */
window.TTB.getVendorProfile = function (partnerKey) {
// get a default instance for internal use
var ttb = window.TTB._createDefaultInstance(partnerKey);
var url = (ttb.baseURL + methodsMapping.VENDOR__GET_PROFILE__VENDOR.endpoint).replace('{{partnerKey}}', partnerKey);
var request = {
url: url,
method: 'GET',
xhrFields: {
withCredentials: false
}
};
return ttb._ajax(request, methodsMapping.VENDOR__GET_PROFILE__VENDOR);
};
/**
* @memberof TTB
* @alias getSponsors
* @static
*
* @description
* [new widget .connectWidget() is recommended.] This static method provides the list of all available sponsors based on given info.
*
* @param {Object} payload - The payload object containing required info against the logged-in user, so that we could suggest the sponsor(s) for it.
* @param {String} [payload.email] - The email address of the logged-in user, if they have signed-up previously for any sponsor(s), we include them.
* @param {String} [payload.zipCode] - The Zip Code of the logged-in user, if user is newly signed-up in TTB system, we list the available sponsors in that region.
*
* @param {String} partnerKey - The partner key provided by support team for the consumer site.
*
* @example
*
* var payload = {
* email: 'agent47@domain.com',
* zipCode: '12345'
* };
*
* TTB.getSponsors(payload)
* .then(function(res) {
* if (res.response.status === 'OK') {
* // your success code here to consume res.response.data
* console.log(res.response.data);
* } else {
* // your failure code here to consume res.response.data
* console.log(res.response.data);
* }
* }, function(err) {
* // your failure code here
* })
* .always(function() {
* // your on-complete code here as common for both success and failure
* });
*
* @return {Object} promise - Jquery AJAX deferred promise is returned which on-success returns the required info.
*
* */
window.TTB.getSponsors = function (payload, partnerKey) {
// get a default instance for internal use
var ttb = window.TTB._createDefaultInstance(partnerKey);
var request = {
method: 'POST',
data: JSON.stringify(payload)
};
return ttb._ajax(request, methodsMapping.SPONSOR__GET_LIST);
};
/**
* @memberof TTB
* @alias getSponsorSelection
* @static
* @private
*
* @description
* This static method gets the sponsor selection that user with given credentials, had performed previously.
*
* @param {String} partnerKey - The partner key provided by support team for the consumer site.
* @param {Object} payload - The payload object containing user information for recognising.
* @param {String} [payload.email] - The email address of the logged-in user.
*
* @example
*
* var partnerKey = 'xxxxxxxxxx';
*
* var payload = {
* email: 'agent47@domain.com'
* };
*
* TTB.getSponsorSelection(partnerKey, payload)
* .then(function(res) {
* if (res.response.status === 'OK') {
* // your success code here to consume res.response.data
* console.log(res.response.data);
* } else {
* // your failure code here to consume res.response.data
* console.log(res.response.data);
* }
* }, function(err) {
* // your failure code here
* })
* .always(function() {
* // your on-complete code here as common for both success and failure
* });
*
* @return {Object} promise - Jquery AJAX deferred promise is returned which on-success returns the required info.
*
* */
window.TTB.getSponsorSelection = function (partnerKey, payload) {
// get a default instance for internal use
var ttb = window.TTB._createDefaultInstance(partnerKey);
var request = {
method: 'POST',
data: JSON.stringify(payload)
};
return ttb._ajax(request, methodsMapping.SPONSOR__GET_SELECTION);
};
/**
* @memberof TTB
* @alias showSelectSponsor
* @static
*
* @description
* [new widget .connectWidget() is recommended.] This static method provides the list of all available sponsors based on given info.
*
* @param {Object} data - Information to be required through the sponsor selection flow.
* @param {Object} [data.performLogin="true"] - To auto-perform login against the selected sponsor.
* @param {Object} data.partnerKey - The partner key provided by support team for the consumer site.
* @param {Object} data.getSponsorsPayload - To be used for <code>getSponsors()</code>. Please see payload information over [TTB.getSponsors()]{@link TTB#.getSponsors}.
* @param {Object} data.loginRemotePayload - To be used for <code>loginRemote()</code>. Please see payload information over [TTB.loginRemote()]{@link TTB#loginRemote}.
* @param {Object} [data.selectedSponsor] - if user has any existing sponsor, provide this to show that sponsor as selected, in the list.
* @param {Object} [data.userProfile] - Alternate way to pass user profile, [Not recommended] (TTB Internal use only).
* @param {Object} actions - The callbacks options to retrieve success and failure info.
* @param {Function} [actions.onConnect] - The callback to be invoked with
* when user is completely connected .ie. done with selecting sponsor *and then accepting their TOS.
* @param {Function} [actions.onSelect] - The callback to be invoked with <code>selectedSponsor</code> when user selects the sponsor.
* @param {Function} [actions.onError] - The callback to be invoked with <code>error</code> {String} message, against whatever step it fails.
*
* @example
*
* var data = {
* partnerKey: 'xxxxxxxxxxxxxxx',
* getSponsorsPayload: {...}
* loginRemotePayload: {...}
* };
*
* actions = {
* onConnect: function(selectedSponsor, loginRemotePayload) {
* // your success code here to wrap things up considering it as a complete callback.
* },
* onSelect: function(selectedSponsor, loginRemotePayload) {
* // your success code here to consume "selectedSponsor"
*
* // you can instantiate the TTB sdk against the selected sponsor.
* // var ttb = new TTB({
* // ...
* // sponsor: selectedSponsor
* // ...
* // });
*
* // OR you can update the sponsor of already instantiated TTB sdk
* // ttb.setSponsor(selectedSponsor);
* },
* onError: function(error, $sponsorModal) {
* // your failure code here consume error / reason {String}
* }
* };
*
* TTB.showSelectSponsor(data, actions);
*
* @return {Object} $modal - JQuery reference to the rendered modal DOMNode.
*
* */
window.TTB.showSelectSponsor = function (data, actions) {
var modalId, $modal, messageTemplate;
modalId = 'ttb-sdk--select-sponsor';
messageTemplate = '<h3>{{message}}</h3>';
// render the sponsors selector content via modal
$modal = this._modal({
id: modalId,
title: 'Please select a Company to Partner with on your Data Integration',
bodyContent: messageTemplate.replace('{{message}}', 'Retrieving list of all available Companies ...')
});
// retrieve the available sponsors
window.TTB.getSponsors(data.getSponsorsPayload, data.partnerKey)
//.fail(function (res) {
.done(function (res) {
var o, errorMessage;
o = {
sponsorsData: undefined,
bodyMarkup: undefined,
sponsorMarkup: undefined,
sponsorsEmailMarkup: undefined,
sponsorsZipMarkup: undefined,
sponsorTOSTemplate: undefined,
sponsorTOSMarkup: undefined,
$sponsorTOSModal: undefined,
resultsMessage: undefined,
tempTargetList: undefined
};
if (res.response.status !== 'OK') {
window.TTB._log(['showSelectSponsor: getSponsors: error in response: ', res.response]);
errorMessage = messageTemplate.replace('{{message}}', 'Failed in retrieving companies list.');
$modal.find('.modal-body').html(errorMessage);
// pass the error response to error callback if provided.
actions.onError && actions.onError(res.response.data[0], $modal);
return;
}
o.sponsorsData = res.response.data;
o.bodyMarkup = [];
o.sponsorsEmailMarkup = [];
o.sponsorsZipMarkup = [];
o.sponsorsOtherMarkup = [];
// iterate over the list and generate the available options
$.each(o.sponsorsData, function (i, sponsor) {
// generate the required action button - check if sponsor is already selected
o.isCurrentSponsor = data.selectedSponsor && (data.selectedSponsor.name === sponsor.vertical_name);
// if already selected - no info needed to render
if (o.isCurrentSponsor) {
o.sponsorActionButton = defaults.sponsorItemSelectedButton;
// else render the info in button to generate the sponsor info later
} else {
o.sponsorActionButton = defaults.sponsorItemSelectButton
.replace('{{sponsorName}}', sponsor.vertical_name)
.replace('{{sponsorTitle}}', sponsor.company_info.company_name)
.replace('{{sponsorSite}}', sponsor.site_url)
.replace('{{sponsorLogoURL}}', sponsor.company_info.logo_url)
.replace('{{sponsorTOSURL}}', sponsor.TOS_content);
}
// parse site url, to show only the domain name
o.sponsorSiteName =
(sponsor.company_info.company_website || '').replace(/http:\/\/www\.|https:\/\/www\./, '');
// generate sponsor info markup
o.sponsorMarkup = defaults.sponsorItemTemplate
//.replace('{{count}}', i + 1)
.replace('{{logoUrl}}', sponsor.company_info.logo_url)
.replace('{{name}}', sponsor.company_info.company_name)
.replace(/(\{\{websiteURL}})/g, sponsor.company_info.company_website || 'javascript:')
.replace(/(\{\{websiteName}})/g, o.sponsorSiteName)
.replace('{{sponsorActionButton}}', o.sponsorActionButton);
// set the target list with respect to match.type
switch (sponsor.match.type) {
case 'email':
o.tempTargetList = o.sponsorsEmailMarkup;
break;
case 'zip':
o.tempTargetList = o.sponsorsZipMarkup;
break;
// considering them as Benutech / leads
case null:
o.tempTargetList = o.sponsorsOtherMarkup;
break;
default:
window.TTB._log(['showSelectSponsor: unknown sponsor.match.type: ', sponsor.match.type]);
// skip addition
return;
}
// add the item to the relevant list
o.sponsorMarkup = o.sponsorMarkup
.replace('{{count}}', o.tempTargetList.length + 1);
o.tempTargetList.push(o.sponsorMarkup);
});
// add match type "email" results.
if (o.sponsorsEmailMarkup.length) {
o.resultsMessage = 'It appears you <strong>currently partner</strong> with the following Companies ...';
o.bodyMarkup.push(defaults.sponsorListTemplate
.replace('{{resultsMessage}}', o.resultsMessage)
.replace('{{sponsorsMarkup}}', o.sponsorsEmailMarkup.join(''))
);
}
// add match type "zip" results.
if (o.sponsorsZipMarkup.length) {
// add a separator if match type "email" results were added too.
//if (o.sponsorsEmailMarkup.length) {
// o.bodyMarkup.push('<hr>');
//}
o.resultsMessage = 'The following Companies are <strong>available Partners</strong> in the <strong>{{zipCode}}</strong> zip code.'
.replace('{{zipCode}}', data.getSponsorsPayload.zipCode);
o.bodyMarkup.push(defaults.sponsorListTemplate
.replace('{{resultsMessage}}', o.resultsMessage)
.replace('{{sponsorsMarkup}}', o.sponsorsZipMarkup.join(''))
);
}
// add match type "null" results - when no zip, email results are there.
if (o.sponsorsOtherMarkup.length && !o.sponsorsZipMarkup.length && !o.sponsorsEmailMarkup.length) {
// check if there is only one result returned AND that it is the "Benutech"
if (o.sponsorsData.length === 1 && o.sponsorsData[0].vertical_name === 'leads') {
o.resultsMessage = [
'There is currently not a Company as a participating Partner in Zip Code <strong>{{zipCode}}</strong>. ',
'The Data will be proudly supplied by Benutech Inc.'
].join('')
.replace('{{zipCode}}', data.getSponsorsPayload.zipCode);
} else {
o.resultsMessage = 'The following Companies are available Partners.';
}
o.bodyMarkup.push(defaults.sponsorListTemplate
.replace('{{resultsMessage}}', o.resultsMessage)
.replace('{{sponsorsMarkup}}', o.sponsorsOtherMarkup.join(''))
);
}
// add the final markup to DOM.
$modal.find('.modal-body').html(o.bodyMarkup.join(''));
// register the click handler for sponsor selection
$('#' + modalId + ' tbody').on('click', 'button', function (e) {
var selectedSponsor;
// TODO refactor - take data directly from sponsor object
selectedSponsor = {
name: $(this).attr('data-sponsor-name'),
title: $(this).attr('data-sponsor-title'),
site: $(this).attr('data-sponsor-site'),
logoURL: $(this).attr('data-sponsor-logo'),
TOSURL: $(this).attr('data-sponsor-tos')
};
// handle value "null" of "site"
if (selectedSponsor.site === 'null') {
selectedSponsor.site = null;
}
// present TOS modal
window.TTB.showSponsorTOSModal(selectedSponsor, actions, {
performLogin: data.performLogin === undefined ? true : data.performLogin,
userProfile: data.userProfile,
partnerKey: data.partnerKey,
loginRemotePayload: data.loginRemotePayload
});
// auto close/hide the select-sponsor modal
$modal.modal('hide');
});
})
.fail(function (err) {
var errorMessage;
errorMessage = messageTemplate
.replace('{{message}}', defaults.errorMessages.GENERAL__CONNECT_FAILED + ' for retrieving companies list.');
$modal.find('.modal-body').html(errorMessage);
// pass the error to error callback if provided.
actions.onError && actions.onError(errorMessage, $modal);
});
// triggering .modal() of bootstrap
return $modal.modal({
//backdrop: 'static'
});
};
/**
* @memberof TTB
* @alias showSponsorTOSModal
* @static
*
* @description
* [new widget .connectWidget() is recommended.] This static method is used as a helper component inside <code>showSelectSponsor()</code> method.
* This method shows a "Thank you" modal for handling TOS against the selected sponsor, after user selected it
* via <code>TTB.showSelectSponsor()</code>.
*
* @param {Object} selectedSponsor - The sponsor information retrieved after selecting a sponsor from the list via <code>TTB.showSelectSponsor()</code>.
* @param {Object} actions - The callbacks options to retrieve success and failure info.
* @param {Function} [actions.onConnect] - The callback to be invoked with
* when user is completely connected .ie. done with selecting sponsor (via TTB.showSelectSponsor()) *and then accepting their TOS.
* @param {Function} [actions.onSelect] - The callback to be invoked with <code>selectedSponsor</code> when user selects the sponsor.
* @param {Function} [actions.onError] - The callback to be invoked with <code>error</code> {String} message, against whatever step it fails.
*
* @param {Object} options - The options to perform additional tasks, e.g. login only for now.
* @param {Object} [options.performLogin="false"] - To auto-perform login against the selected sponsor.
* @param {Object} options.partnerKey - The partner key provided by support team for the consumer site.
* @param {Object} options.loginRemotePayload - "stk" and "getuser_url" information to be used for login. please check .loginRemote() documentation for more.
* @param {Object} [options.userProfile] - Alternate way to pass user profile, [Not recommended] (TTB Internal use only).
* @example
*
* var selectedSponsor = { ... }; // received from options.onSelect of TTB.showSelectSponsor()
*
* var actions = {
* onConnect: function(selectedSponsor, loginRemotePayload) {
* // your success code here to wrap things up considering it as a complete callback.
* },
* onSelect: function(selectedSponsor, loginRemotePayload) {
* // your success code here to consume "selectedSponsor"
*
* // you can instantiate the TTB sdk against the selected sponsor.
* // var ttb = new TTB({
* // ...
* // sponsor: selectedSponsor
* // ...
* // });
*
* // OR you can update the sponsor of already instantiated TTB sdk
* // ttb.setSponsor(selectedSponsor);
* },
* onError: function(error, $sponsorModal) {
* // your failure code here
* }
* };
*
* var options = {
* partnerKey: 'xxxxxxxxxxxxxxx',
* performLogin: true,
* loginRemotePayload: {...}
* };
*
* TTB.showSponsorTOSModal(selectedSponsor, actions, options);
*
* @return {Object} $modal - JQuery reference to the rendered modal DOMNode.
*
* */
window.TTB.showSponsorTOSModal = function (selectedSponsor, actions, options) {
var ttb, modalId, $modal, headingTemplate, headingMarkup, bodyTemplate, bodyMarkup;
options = options || {};
modalId = 'ttb-sdk--sponsor-tos-modal';
// create an instance against the selected sponsor.
ttb = window.TTB._createDefaultInstance(options.partnerKey, selectedSponsor);
// define modal component templates.
headingTemplate = [
'Thank you for choosing <br>{{sponsorTitle}} <br> as your partner. <br>',
'<p id="ttb-sdk--sponsor-tos-message">Please read and agree to {{sponsorTitle}} <a href="{{sponsorTOSURL}}" target="_blank">Terms and conditions</a></p>'
].join('');
bodyTemplate = [
'<div class="form-group form-check" id="ttb-sdk--sponsor-tos-check">',
' <input id="ttb-sdk--sponsor-tos-agree" type="checkbox" class="form-check-input">',
' <label class="form-check-label" for="ttb-sdk--sponsor-tos-agree">I hereby agree</label>',
'</div>',
'<button id="ttb-sdk--sponsor-selection-finish" type="button" class="btn btn-success btn-block" disabled>Finish</button>'
].join('');
headingMarkup = headingTemplate
.replace(/\{\{sponsorTitle}}/g, selectedSponsor.title)
.replace('{{sponsorTOSURL}}', selectedSponsor.TOSURL);
bodyMarkup = bodyTemplate;
//.replace('{{sponsorTitle}}', selectedSponsor.title);
// render the sponsors selector content via modal
$modal = window.TTB._modal({
id: modalId,
sizeClass: ' ',
title: headingMarkup,
bodyContent: bodyMarkup
});
// registers the click handler for accept sponsor TOS
$('#ttb-sdk--sponsor-tos-agree').on('change', TOSAgreeChange);
$('#ttb-sdk--sponsor-selection-finish').on('click', saveSponsor);
// handle login-free flow; redirect user to the vertical site for login and TOS.
if (!options.performLogin) {
// hide TOS message, and check.
$('#ttb-sdk--sponsor-tos-message').hide();
$('#ttb-sdk--sponsor-tos-check').hide();
// enable finish button without TOS agree check
$('#ttb-sdk--sponsor-selection-finish').prop('disabled', false);
}
// triggering .modal() of bootstrap
return $modal.modal({
//backdrop: 'static'
});
function TOSAgreeChange() {
var isChecked = $(this).prop('checked');
$('#ttb-sdk--sponsor-selection-finish').prop('disabled', !isChecked);
}
function utilUpdateButton(text, disable) {
$('#ttb-sdk--sponsor-selection-finish')
.text(text || '')
.prop('disabled', disable);
}
// handles the error by invoking the related action callback.
function utilHandleError(reason) {
actions.onError && actions.onError(reason, $modal);
}
// saves the sponsor selection over server, and to pass the control to the caller
function saveSponsor() {
var payload;
window.TTB._log(['saveSponsor: called']);
// disable accept button.
utilUpdateButton('Saving Selection...', true);
// handle SAML flow, where we don't have the stk yet.
payload = !options.userProfile ? options.loginRemotePayload : {
data: {
User: options.userProfile
}
};
//ttb.saveSponsorSelection(options.loginRemotePayload, true)
ttb.saveSponsorSelection(payload, false)
.then(function (res) {
res = res.response;
if (res.status === 'OK') {
window.TTB._log(['saveSponsor: success', res]);
utilUpdateButton('Selection Saved!', true);
// handle SAML flow - fill up the loginRemotePayload, and bypass login, and TOS, to take user to vertical site.
if (options.userProfile) {
options.loginRemotePayload.stk = res.data.stk;
options.loginRemotePayload.getuser_url = res.data.getuser_url;
}
// invoke onSelect callback with selectedSponsor and loginRemotePayload info.
// for SAMLflow - from onSelect callback, user is taken to the vertical site to continue.
actions.onSelect && actions.onSelect(selectedSponsor, options.loginRemotePayload, $modal);
// authenticate user before request for TOS.
options.performLogin && performLogin();
} else {
window.TTB._log(['saveSponsor: failed', res]);
utilHandleError(res.data[0]);
}
}, function (reason) {
window.TTB._log(['saveSponsor: error', reason]);
utilHandleError(defaults.errorMessages.GENERAL__CONNECT_FAILED + ' for saving partner.');
});
}
// performs a remote login using given stk, so that to forward with accept TOS
function performLogin() {
window.TTB._log(['performLogin: called']);
// show progress on button
utilUpdateButton('Authenticating...', true);
//ttb.saveSponsorSelection(options.loginRemotePayload, true)
ttb.loginRemote(options.loginRemotePayload, false)
.then(function (res) {
res = res.response;
if (res.status === 'OK') {
window.TTB._log(['performLogin: success', res]);
// since login is successful, show TOS modal to let user accept.
TOSAccept();
} else {
window.TTB._log(['performLogin: failed', res]);
utilHandleError(res.data[0]);
}
}, function (reason) {
window.TTB._log(['performLogin: error', reason]);
utilHandleError(defaults.errorMessages.GENERAL__CONNECT_FAILED + ' for login.');
});
}
// performs an ajax call to accept selected sponsor TOS.
function TOSAccept() {
var request;
window.TTB._log(['TOSAccept: called']);
// show progress on button
utilUpdateButton('Accepting Terms...', true);
request = {
method: 'GET'
};
return ttb._ajax(request, methodsMapping.SPONSOR__ACCEPT_TOS)
.then(function (res) {
res = res.response;
window.TTB._log(['TOSAccept: complete', res]);
if (res.status === 'OK') {
window.TTB._log(['TOSAccept: success', res]);
// invoke connect callback with the selectedSponsor
actions.onConnect && actions.onConnect(selectedSponsor, options.loginRemotePayload, $modal);
utilUpdateButton('Connected !', false);
} else {
window.TTB._log(['TOSAccept: failed', res]);
// invoke done callback with selectedSponsor
utilHandleError(res.data.msg); // not data[0]
}
}, function (reason) {
window.TTB._log(['TOSAccept: error', reason]);
utilHandleError(defaults.errorMessages.GENERAL__CONNECT_FAILED + ' for accepting Terms.');
});
}
};
/**
* @memberof TTB
* @alias renderLogoWidget
* @static
*
* @description
* This static method renders a logo link on vendors's sites, clicking which can take user to the landing page for TTB powered widgets.
* <br>
* (Check it out in action over https://jsfiddle.net/benutech/w0ya3qr5/)
*
* @param {String} elementSelector - DOM element selector where the widget needs to be rendered.
* <code>#lorem</code> or <code>.ipsum</code> etc.
*
* @param {Object} vendorInfo - The information regarding vendor, and it's user session details. for details, Check out utilGenerateLandingPageURL() documentation.
* @example
*
* var elementSelector = '#ttb--render-logo-wrapper';
*
* var vendorInfo = {
* stk: "xxxxxxxxxxxxxxx",
* getuser_url: 'https://www.yoursite.com/webservices/getuser.json',
* partnerKey: 'xxxxxxxxxxxxxxxxx'
* };
*
* TTB.renderLogoWidget(elementSelector, vendorInfo);
*
* @return {Object} wrapperEL - DOM Node reference to the rendered widget's wrapper.
*
* */
window.TTB.renderLogoWidget = function (elementSelector, vendorInfo) {
var landingPageUrl, template, markup, wrapperEL;
landingPageUrl = window.TTB.utilGenerateLandingPageURL(vendorInfo);
template = [
'<a class="ttb-sdk--render-logo--link" target="_blank" href="{{landingPageUrl}}" title="Title ToolBox - Full Profile and Net Sheet Report Lookup">',
' <img class="ttb-sdk--render-logo--image" src="https://demo.titletoolbox.com/assets/images/logo_in.png" style="background-color: #389ae5;">',
'</a>'
].join('');
markup = template
.replace('{{landingPageUrl}}', landingPageUrl);
// we do not use jquery, to lose our dependency for this widget.
wrapperEL = document.querySelector(elementSelector);
// keep existing markup, just extend it.
wrapperEL.innerHTML += markup;
// return the reference for any further usage of element.
return wrapperEL;
};
/**
* @memberof TTB
* @alias utilGenerateLandingPageURL
* @static
*
* @description
* This static method generates a link / URL to use on vendors's site, clicking which can take user to the landing page for TTB powered widgets.
* <br>
* it is used inside renderLogoWidget(), To learn more about it, check out its documentation.
*
* @param {Object} vendorInfo - The information regarding vendor, and it's user.
* @param {String} vendorInfo.stk - The session token from existing login at 3rd-party app.
* @param {String} vendorInfo.getuser_url - The URL to hit to get the user information against the given stk.
* @param {Object} vendorInfo.partnerKey - The partner key provided by support team for the consumer site.
* @example
*
* var vendorInfo = {
* stk: "xxxxxxxxxxxxxxx",
* getuser_url: 'https://www.yoursite.com/webservices/getuser.json',
* partnerKey: 'xxxxxxxxxxxxxxxxx'
* };
*
* var TTBLandingPageUrl = TTB.utilGenerateLandingPageURL(vendorInfo);
* // you can use this variable as HREF to any anchor (a) tag.
* console.log(TTBLandingPageUrl);
*
* @return {string} TTBLandingPageUrl - A string URL contains provided info in an encoded format, as part of URL.
*
* */
window.TTB.utilGenerateLandingPageURL = function (vendorInfo) {
var TTBLandingPageUrl;
// dev vs prod destination.
TTBLandingPageUrl = window.location.port === defaults.devPortSandbox ?
('http://localhost:' + defaults.devPortLanding): 'https://ttb-landing-page.herokuapp.com';
TTBLandingPageUrl += '/?stk={{stk}}&getuser_url={{getuser_url}}&partnerKey={{partnerKey}}&enabled_features={{enabled_features}}&debug={{debug}}'
.replace('{{stk}}', vendorInfo.stk)
.replace('{{getuser_url}}', encodeURIComponent(vendorInfo.getuser_url))
.replace('{{partnerKey}}', vendorInfo.partnerKey)
.replace('{{enabled_features}}', defaults.enabledFeatures)
.replace('{{debug}}', window.TTB.debug);
return TTBLandingPageUrl;
};
/**
* @memberof TTB
* @alias _utilAddSessionToken
* @static
*
* @description
* This static method extends provided object (if any) with the session-token (if any) stored by last login attempt by loginRemote() or login().
*
* @param {Object} [info] - any object to extend. E.g. query params converted object by _.ajax()
*
* @example
*
* var queryParams = { ... }; // your stuff
* var infoContainsSessionToken = TTB._utilAddSessionToken(queryParams);
*
* @return {Object | undefined} info - extended object with session token (if any)
*
* */
window.TTB._utilAddSessionToken = function (info) {
// attach only when user is logged in.
var sessionToken = TTB._getLocal(defaults.sessionKeyName);
if (sessionToken) {
// define one if no object provided
info = info || {};
// extend and return
info[defaults.sessionKeyName] = sessionToken;
return info;
}
// else - return what provided as it is. object or undefined
return info;
};
/**
* @memberof TTB
* @alias utilPromiseReject
* @static
*
* @description
* This static method returns a promise with rejection, holding the passed reason.
*
* @param {Object | String | any} reason - reason of rejection. It can be an object, a string, or anything.
*
* @example
*
* var rejectedPromise = TTB.utilPromiseReject('Something went wrong');
*
* // or
*
* var rejectedPromise = TTB.utilPromiseReject({foo: 'Something useful as a detailed reason.'});
*
* @return {Promise} promise - promise with rejection state, holding the passed reason.
*
* */
window.TTB.utilPromiseReject = function (reason) {
var deferred;
deferred = $.Deferred();
deferred.reject(reason);
return deferred;
};
/**
* @memberof TTB
* @alias utilPromiseResolve
* @static
*
* @description
* This static method returns a promise with resolution, holding the passed information.
*
* @param {Object | String | any} info - info for resolution. It can be an object, a string, or anything.
*
* @example
*
* var resolvedPromise = TTB.utilPromiseResolve('Successful operation !');
*
* // or
*
* var resolvedPromise = TTB.utilPromiseResolve({foo: 'Something useful as a detailed info.'});
*
* @return {Promise} promise - promise with resolved state, holding the passed info.
*
* */
window.TTB.utilPromiseResolve = function (info) {
var deferred;
deferred = $.Deferred();
deferred.resolve(info);
return deferred;
};
/**
* @memberof TTB
* @alias utilCopy
* @static
*
* @description
* This static method returns a one-level-only deep copy / clone of provided info object.
*
* @param {Object} info - object to copy / clone.
*
* @example
*
* var detail = {foo: 'xoxo'};
* var cloneOfDetail = TTB.utilCopy(detail);
*
* @return {Object | String | any} copy - copy / clone of provided info object.
*
* */
window.TTB.utilCopy = function (info) {
return $.extend({}, info);
};
/** @lends TTB.prototype */
window.TTB.prototype = {
/**
* Logs the arguments based on debug flag.
* @private
*
* @param {Array} args - list of values to be logged.
* */
_log: function (args) {
this.config.debug && console.log.apply(console, [defaults.sdkPrefix + ' :', this.instanceId.toString(), ':'].concat(args));
},
/**
* This method auto fills the form fields having data-ttb-field="", against the given data.
*@private
*
* @param {String} selector - A query selector of context (form/parent element)
* @param {Object} data - A data object retrieved from the response of certain of API, against which auto-fill needs to be processed.
* @param {Boolean} [clearExisting="false"] - clear the existing / previously added values to all of the fields that are to be auto-filled.
* @param {Number} [delay="0"] - A delay in milliseconds, useful when wished to visualize auto filling. (50 is a good value to test)
*
* */
_fillFields: function (selector, data, clearExisting, delay) {
var _self, $fields, model, modelValue, fillValue, timeout;
_self = this;
$fields = $(selector).find('input[' + _self.autoFillAttr + ']');
// if delay is given, wrap the fillValue within a setTimeout.
if (delay) {
timeout = 0;
fillValue = function($element, modelValue) {
// how much to delay between each field
timeout += delay;
setTimeout(function(){
$element.val(modelValue);
}, timeout);
};
} else {
fillValue = function(modelValue) {
$(this).val(modelValue);
};
}
$fields.each(function () {
model = $(this).attr(_self.autoFillAttr);
if (clearExisting) {
$(this).val(undefined);
}
try {
modelValue = eval('data.' + model);
modelValue && fillValue($(this), modelValue);
} catch (e) {
_self._log(['autoFill : ', model, ': skipping - invalid model']);
}
});
},
/**
* This method looks for the input field against the given fieldName as value of its data-ttb-field="" attribute,
* to auto-fill it with the passed fieldValue.
*@private
*
* @param {String} selector - A query selector of context (form/parent element)
* @param {String} fieldName - the name specified via data-ttb-field="" attribute of input element.
* @param {String | Number } fieldValue - the value to be filled with given input
*
* */
_fillField: function (selector, fieldName, fieldValue) {
var $field;
$field = $(selector).find('input[' + this.autoFillAttr + '="' + fieldName + '"]');
if ($field.length) {
$field.val(fieldValue);
} else {
this._log(['autoFill : skipping : field not found - ', fieldName]);
}
},
/**
* Triggers the request with all required headers, cookies, etc.
* @param options {Object} - The configuration to pass to $.ajax .
* @param mapping {Object} - Certain method's mapping info that contains its methodName and the endpoint of the webservice it consumes.
* @param [queryParams] {Object} - key-value paired info that needs to be passed as query params string.
* @private
*
* */
_ajax: function (options, mapping, queryParams) {
var _self, request;
_self = this;
// take the full URL or build it up using baseURL and the given endpoint
options.url = options.url || (this.baseURL + mapping.endpoint);
// extend given AJAX options with required Headers, and CORS flag
request = $.extend(options, {
// TTB Sandbox required headers
headers: {
'Partner-Key': this.config.partnerKey,
'Third-Party': true
},
// allow CORS
xhrFields: options.xhrFields || {
//withCredentials: true
}
});
// custom data to attach with requests for internal additional logics.
request.customData = {
// keep original url for repeating requests (to avoid multiple times attaching query params)
originalUrl: request.url
};
// proceed with the request using configuration
return _self._ajaxProceed(request, mapping, queryParams);
},
/**
* Proceed the ajax request after configuration has been build from _ajax(), or when retried from _handleSessionExpire()
* @param request {Object} - The configuration built from _ajax() call.
* @param mapping {Object} - Check _ajax() for details.
* @param [queryParams] {Object} - Check _ajax() for details.
*
* @private
*
* */
_ajaxProceed: function (request, mapping, queryParams) {
var _self, o;
_self = this;
o = {};
// check for type of the method. public vs auth-protected methods.
o.isPublicAPIMethod = defaults.sessionKeySkippedMethods.indexOf(mapping.methodName) >= 0;
// if its not a public API, send session id (TTBSID query param)
if (!o.isPublicAPIMethod) {
// attach only when user is logged in. - most probably user would be logged in this scenario.
queryParams = TTB._utilAddSessionToken(queryParams);
}
// reset url to original Url (which is without query params)
request.url = request.customData.originalUrl;
// append query params (if any)
o.paramsString = queryParams ? $.param(queryParams) : '';
request.url += o.paramsString ? '?' + o.paramsString : '';
return $.ajax(request)
.then(function (res) {
if (typeof res === 'string' || res instanceof Array || (res.response || res).status === 'OK') {
_self._log([mapping.methodName + '() [', mapping.endpoint , ']', ': success :', res]);
} else {
_self._log([mapping.methodName + '() [', mapping.endpoint , ']', ': error :', res]);
}
return res;
}, function (error) {
_self._log([mapping.methodName + '() [', mapping.endpoint , ']', ': fail :', error]);
// exit with error if it is not a session-expire response.
if (error.status !== 401) {
return error;
}
// handle session expire / no authentication
return _self._handleSessionExpire(error, request, mapping);
});
//.always(function(arg) {
// _self._log([mapping.methodName + '() [', mapping.endpoint , ']', ': always :', arg]);
// return arg;
//});
},
/**
* Handles the failed requests due to session expire or not authenticated.
* @param error {Object} - error object from the failed _ajaxProceed() callback.
* @param request {Object} - Check _ajax() for details
* @param mapping {Object} - Check _ajax() for details
* @private
*
* */
_handleSessionExpire: function (error, request, mapping) {
var _self, sessionExpireInfo, renewSessionPromise, messages, sessionExpiredHandler;
_self = this;
sessionExpireInfo = {
requestConfig: request,
requestError: error,
methodName: mapping.methodName,
endpoint: mapping.endpoint,
};
// DEPRECATED !
// case: deprecated and removed onSessionExpire() provided.
if (_self.config.onSessionExpire) {
_self._log(['_handleSessionExpire: Support for onSessionExpire() has been removed after deprecating it in various past versions. ' +
'Ignoring the passage.']);
}
sessionExpiredHandler = _self.config.onSessionExpireV3 || _self.config.onSessionExpireV2;
messages = {
couldNotHandle: '_handleSessionExpire: ERROR - Could not handle.',
checkDocs: 'Please check documentation for more details.',
};
// case: No sessionExpiredHandler was provided - Exit.
if (!sessionExpiredHandler) {
_self._log([
messages.couldNotHandle,
'Neither config.onSessionExpireV3() nor config.onSessionExpireV2() was provided.',
messages.checkDocs
]);
// exist with error
return error;
}
// case: config.onSessionExpireV2() was provided - handle with it.
_self._log(['_handleSessionExpire:', mapping.methodName + '() [', mapping.endpoint , ']: handling with provided onSessionExpire handler.']);
// TODO enhance to use call once sessionExpire handler only once, until promise get fulfilled.
// call to let host app return a new/valid STK (e.g. host app prompt user for a login, OR return immediately a new STK, or uuid)
renewSessionPromise = _self.config.onSessionExpireV2(sessionExpireInfo);
// case: no promise returned from the onSessionExpireV2() of the host app
if (!renewSessionPromise) {
_self._log([
messages.couldNotHandle,
'Expected a promise in return from onSessionExpireV2()',
messages.checkDocs
]);
// exist with error
return error;
}
return renewSessionPromise
.then(function(authConfig) {
var _authConfig;
// case: onSessionExpireV2 was provided. auto perform the adjustments.
if (_self.config.onSessionExpireV2) {
_authConfig = {};
_authConfig.authMethod = 'loginRemote';
_authConfig.payload = authConfig; // it is loginRemotePayload in this case.
// case: for onSessionExpireV3
} else {
_authConfig = authConfig;
}
// case: promise resolved with invalid authConfig or loginRemotePayload info.
if (!_authConfig || !_authConfig.authMethod || !_authConfig.payload || !(_authConfig.payload.uuid || (_authConfig.payload.stk && _authConfig.payload.getuser_url))) {
_self._log([
messages.couldNotHandle,
'onSessionExpire: Returned promise resolved with invalid authConfig or loginRemotePayload info.',
messages.checkDocs
]);
// reject and exist with error
return window.TTB.utilPromiseReject(error);
}
// case: promise resolved with valid authConfig info
// perform a login. e.g. using loginRemote() or loginUUID()
return _self[_authConfig.authMethod](_authConfig.payload)
.then(function (res) {
_self._log(['_handleSessionExpire: loginRemote: success:']);
// retry the failed request now
return _self._ajaxProceed(request, mapping);
}, function (reason) {
_self._log([
messages.couldNotHandle,
'Could not perform login using provided loginRemotePayload info.',
messages.checkDocs,
reason
]);
// exist with error
return error;
});
// case: could not retrieve a valid STK
}, function (reason) {
_self._log([
messages.couldNotHandle,
'onSessionExpire: returned Promise rejected.',
messages.checkDocs,
reason
]);
// exist with error
return error;
});
},
/**
* This method is used to switch to a different sponsor (Title Company) and it generates a new <code>baseURL</code>
* based on passed <code/>sponsor.name</code> with existing <code>baseURLPattern</code>.
*
* @param {Object} sponsor - Information to be retrieved via <code>options.onSelect()</code> of <code>TTB.getSponsors()</code>
* @param {String} sponsor.name - The <code>vertical_name</code> field value of sponsor object retrieved. (to be used in generating baseURL)
* @param {String} sponsor.title - The <code>company_info.company_name</code> field value of sponsor object retrieved.
* @param {String} sponsor.site - The <code>site_url</code> field value of sponsor object retrieved.
* @param {String} sponsor.logoURL - The <code>company_info.logo_url</code> field value of sponsor object retrieved.
* @param {String} sponsor.TOSURL - The <code>TOS_content</code> field value of sponsor object retrieved.
*
* @return {String} baseURL - The newly generated <code>baseURL</code>.
*
* @example
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* ttb.setSponsor({
* name: 'direct',
* title: 'Benutech',
* site: 'http://direct.titletoolbox.com',
* logoURL: 'https://s3-us-west-1.amazonaws.com/titletoolbox/company+logos/Benutech/Benute+Logo.png',
* TOSURL: 'https://direct.api.titletoolbox.com/pages/tos/direct_tos'
* });
*
* */
setSponsor: function (sponsor) {
this._log(['setSponsor: existing:', this.sponsor && this.sponsor.name, '| new:', sponsor.name]);
// store in local storage for later use.
window.TTB._setLocal(defaults.localStorageNames.connect__selectedSponsor, sponsor);
// update sponsor in instance object
this.sponsor = sponsor;
// generate baseURL based of newly selected sponsor.
return this.baseURL = this.baseURLPattern.replace('{{sponsorName}}', sponsor.name);
},
/**
* This method is used to open up a TOS (Terms of Service) Modal. which lists TOS info of the selected sponsor.
*
* @example
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* ttb.showTOS();
*
* @return {Object} $modal - JQuery reference to the rendered modal DOMNode.
*
* */
showTOS: function () {
var modalId, $modal, modalTemplate;
modalId = 'ttb-sdk--sponsor-tos';
modalTemplate = [
'<iframe src="{{src}}" width="100%" height="600px"></iframe>',
//'<div class="row">',
//' <div class="col-xs-12">',
//' <button class="btn btn-success ttb-sdk--tos-accept pull-right">Accept</button>',
//' </div>',
'</div>'
]
.join('')
.replace('{{src}}', this.sponsor.TOSURL);
// render the sponsors TOS content via modal
$modal = window.TTB._modal({
id: modalId,
title: 'Terms of Service',
bodyContent: modalTemplate
});
// triggering .modal() of bootstrap
return $modal.modal({
//backdrop: 'static'
});
},
/**
* This method is used after selecting a sponsor via the static function <code>TTB.showSelectSponsor(...)</code> to open up a Thank you modal,
* via which user can agree to TOS (Terms of Service) of the selected sponsor.
*
*
* @example
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* ttb.handleSponsorTOS(selectedSponsor);
*
* @return {Object} $modal - JQuery reference to the rendered modal DOMNode.
*
* */
handleSponsorTOS: function (selectedSponsor) {
var modalId, $modal, modalTemplate;
modalId = 'ttb-sdk--handle-sponsor-tos';
// update the instance sponsor
this.setSponsor(selectedSponsor);
modalTemplate = [
'<iframe src="{{src}}" width="100%" height="600px"></iframe>',
//'<div class="row">',
//' <div class="col-xs-12">',
//' <button class="btn btn-success ttb-sdk--tos-accept pull-right">Accept</button>',
//' </div>',
'</div>'
]
.join('')
.replace('{{src}}', this.sponsor.TOSURL);
// render the sponsors TOS content via modal
$modal = window.TTB._modal({
id: modalId,
title: 'Terms of Service',
bodyContent: modalTemplate
});
// triggering .modal() of bootstrap
return $modal.modal({
//backdrop: 'static'
});
},
/**
* This method is used to authenticate the vendor site using one uuid (that was provided for their specified sites only),
* and to maintain a session for their users. <br>
* It is similar to loginRemote(), but this one is generic i.e. not user specific authentication. rather authenticating their sites.
*
* @param {Object} payload - The payload object containing required info
* @param {String} payload.uuid - The uuid string provided to vendor to use from their specified sites.
*
* @example
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* var payload = {
* uuid: "xxxxxxxxxxxxxxx"
* };
*
* ttb.loginUUID(payload)
* .then(function(res) {
* if (res.response.status === 'OK') {
* // user is successfully logged-in !!
* // your success code here to consume res.response.data for logged-in user info
* console.log(res.response.data);
* } else {
* // your failure code here to consume res.response.data for validation errors info
* console.log(res.response.data);
* }
* }, function(err) {
* // your failure code here
* })
* .always(function() {
* // your on-complete code here as common for both success and failure
* });
*
* @return {Object} promise - Jquery AJAX deferred promise is returned which on-success returns the required info.
* */
loginUUID: function (payload) {
var _self;
_self = this;
var request = {
method: 'POST',
data: JSON.stringify(payload)
};
return this._ajax(request, methodsMapping.AUTH__LOGIN_UUID)
.then(function (res) {
var sessionToken;
// if user is successfully logged-in !!
if (res.response.status === 'OK') {
// store sessionToken (vertical-stk) in local-storage for later usage against each API key
sessionToken = res.response.data[defaults.sessionKeyName];
TTB._setLocal(defaults.sessionKeyName, sessionToken);
_self._log(['loginUUID:', defaults.sessionKeyName, 'updated in localStorage.', TTB._getLocal(defaults.sessionKeyName)]);
}
return res;
});
},
/**
* This method is used to log the user in from a 3rd-party site, and maintain a session for the user throughout the App.
*
* @param {Object} payload - The payload object containing required info
* @param {String} payload.stk - The session token from existing login at 3rd-party app.
* @param {String} payload.getuser_url - The URL to hit to get the user information against the given stk.
*
* @example
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* var payload = {
* stk: "xxxxxxxxxxxxxxx"
* };
*
* ttb.loginRemote(payload)
* .then(function(res) {
* if (res.response.status === 'OK') {
* // user is successfully logged-in !!
* // your success code here to consume res.response.data for logged-in user info
* console.log(res.response.data);
* } else {
* // your failure code here to consume res.response.data for validation errors info
* console.log(res.response.data);
* }
* }, function(err) {
* // your failure code here
* })
* .always(function() {
* // your on-complete code here as common for both success and failure
* });
*
* @return {Object} promise - Jquery AJAX deferred promise is returned which on-success returns the required info.
* */
loginRemote: function (payload) {
var _self;
_self = this;
payload.sso = false;
var request = {
method: 'POST',
data: JSON.stringify(payload)
};
return this._ajax(request, methodsMapping.AUTH__LOGIN_REMOTE)
.then(function (res) {
var sessionToken;
// if user is successfully logged-in !!
if (res.response.status === 'OK') {
// store sessionToken (vertical-stk) in local-storage for later usage against each API key
sessionToken = res.response.data[defaults.sessionKeyName];
TTB._setLocal(defaults.sessionKeyName, sessionToken);
_self._log(['loginRemote:', defaults.sessionKeyName, 'updated in localStorage.', TTB._getLocal(defaults.sessionKeyName)]);
}
return res;
});
},
/**
* This method is used to log the user in and maintain a session for the user throughout the App.
*
* @param {Object} payload - The payload object containing required info
* @param {String} payload.username - the email/username used while signing up
* @param {String} payload.password - the secret password of the account
*
* @example
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* var payload = {
* TbUser: {
* username: "awesomeuser99@domain.com",
* password: "secret_Password0"
* }
* };
*
* ttb.login(payload)
* .then(function(res) {
* if (res.response.status === 'OK') {
* // user is successfully logged-in !!
* // your success code here to consume res.response.data for logged-in user info
* console.log(res.response.data);
* } else {
* // your failure code here to consume res.response.data for validation errors info
* console.log(res.response.data);
* }
* }, function(err) {
* // your failure code here
* })
* .always(function() {
* // your on-complete code here as common for both success and failure
* });
*
* @return {Object} promise - Jquery AJAX deferred promise is returned which on-success returns the required info.
* */
login: function (payload) {
var request = {
method: 'POST',
data: JSON.stringify(payload)
};
return this._ajax(request, methodsMapping.AUTH__LOGIN)
.then(function (res) {
var sessionToken;
// if user is successfully logged-in !!
if (res.response.status === 'OK') {
// store sessionToken in local-storage for later usage against each API key
sessionToken = res.response.data[defaults.sessionKeyName];
TTB._setLocal(defaults.sessionKeyName, sessionToken);
}
return res;
});
},
/**
* Logs out from the TTB webservices server
*
* @example
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* ttb.logout()
* .then(function(res) {
* if (res.response.status === 'OK') {
* // user is successfully logged-out!!
* // your success code here to clear any cached info etc from the web page
* console.log(res.response.data);
*
* } else {
* // your failure code here to consume res.response.data
* console.log(res.response.data);
* }
* }, function(err) {
* // your failure code here
* })
* .always(function() {
* // your on-complete code here as common for both success and failure
* });
*
* @return {Object} promise - Jquery AJAX deferred promise is returned which on-success returns the required info.
* */
logout: function () {
var request = {
method: 'GET'
};
return this._ajax(request, methodsMapping.AUTH__LOGOUT);
},
/**
* Search a property by APN.
*
* @param {Object} payload - The payload object containing required info
* @param {String} payload.parcel_number - The Parcel Number against the property
* @param {String} payload.state_county_fips - The State County FIPS against the property
*
* @example
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* var payload = {
* parcel_number: "46327216",
* state_county_fips: "06059"
* };
*
* ttb.searchByParcelNumber(payload)
* .then(function(res) {
* if (res.response.status === 'OK') {
* // your success code here to consume res.response.data
* console.log(res.response.data);
* } else {
* // your failure code here to consume res.response.data
* console.log(res.response.data);
* }
* }, function(err) {
* // your failure code here
* })
* .always(function() {
* // your on-complete code here as common for both success and failure
* });
*
* @return {Object} promise - Jquery AJAX deferred promise is returned which on-success returns the required info.
* */
searchByParcelNumber: function (payload) {
var request = {
method: 'POST',
data: JSON.stringify(payload)
};
return this._ajax(request, methodsMapping.SEARCH_PROPERTY__PARCEL);
},
/**
* Search a property by site address.
*
* @param {Object} payload - The payload object containing required info - (At least any of the following is required)
* @param {string} [payload.site_address] - Property House# or Street# with the route e.g. "317 2nd St".
* @param {string} [payload.site_unit] - Unit# of the property (If has any).
* @param {string} [payload.site_city] - Property City e.g. "Huntington Beach"
* @param {string} [payload.site_state] - Property State e.g. "CA"
* @param {string} [payload.site_street_number] - Property Street# e.g. "317"
* @param {string} [payload.site_route] - Property Route - "2nd St".
*
* @example
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* var payload = {
* site_address: "317 2nd St",
* site_unit: "",
* site_city: "Huntington Beach",
* site_state: "CA",
* site_zip: "92648",
* site_street_number: "317",
* site_route: "2nd St"
* };
*
* ttb.searchBySiteAddress(payload)
* .then(function(res) {
* if (res.response.status === 'OK') {
* // your success code here to consume res.response.data
* console.log(res.response.data);
* } else {
* // your failure code here to consume res.response.data
* console.log(res.response.data);
* }
* }, function(err) {
* // your failure code here
* })
* .always(function() {
* // your on-complete code here as common for both success and failure
* });
*
* @return {Object} promise - Jquery AJAX deferred promise is returned which on-success returns the required info.
* */
searchBySiteAddress: function (payload) {
var request = {
method: 'POST',
data: JSON.stringify(payload)
};
return this._ajax(request, methodsMapping.SEARCH_PROPERTY__ADDRESS);
},
/**
* Search a property by owners name.
*
* @param {Object} payload - The payload object containing required info - (At least any of the following is required)
* @param {String} [payload.first_name] - Owner's First Name
* @param {String} [payload.last_name] - Owner's Last Name
* @param {String} [payload.state_county_fips] - State County FIPS of the property
*
* @example
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* var payload = {
* first_name: "Fariba",
* last_name: "Siddiqi",
* state_county_fips: "06059"
* };
*
* ttb.searchByOwnerName(payload)
* .then(function(res) {
* if (res.response.status === 'OK') {
* // your success code here to consume res.response.data
* console.log(res.response.data);
* } else {
* // your failure code here to consume res.response.data
* console.log(res.response.data);
* }
* }, function(err) {
* // your failure code here
* })
* .always(function() {
* // your on-complete code here as common for both success and failure
* });
*
* @return {Object} promise - Jquery AJAX deferred promise is returned which on-success returns the required info.
* */
searchByOwnerName: function (payload) {
var request = {
method: 'POST',
data: JSON.stringify(payload)
};
return this._ajax(request, methodsMapping.SEARCH_PROPERTY__OWNER);
},
/**
* This will allow you to order a report from the service. The available reports will depend on your account set up.
*
* @param {Object} payload - The payload object containing required info.
* @param {String} payload.sa_property_id - Unique ID of the property
* @param {String} payload.state_county_fips - State FIPS of the property
* @param {String} [payload.output="link"] - Format of output, supported types are "link", and "html".
* @param {String} [payload.report_type="property_profile"] - The report type, supported types are "single_page_profile", "avm"(*), "prep"(*), "tax_bill" and "property_profile".
*
* @example
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* var payload = {
* sa_property_id: "0039025849",
* state_county_fips: "06059",
* report_type: "property_profile",
* output: "link",
* };
*
* ttb.orderReport(payload)
* .then(function(res) {
* if (res.response.status === 'OK') {
* // your success code here to consume res.response.data
* console.log(res.response.data);
* } else {
* // your failure code here to consume res.response.data
* console.log(res.response.data);
* }
* }, function(err) {
* // your failure code here
* })
* .always(function() {
* // your on-complete code here as common for both success and failure
* });
*
* @return {Object} promise - Jquery AJAX deferred promise is returned which on-success returns the required info.
* */
orderReport: function (payload) {
// setup the defaults
payload.output = payload.output || 'link';
payload.report_type = payload.report_type || 'property_profile';
var request = {
method: 'POST',
data: JSON.stringify(payload)
};
return this._ajax(request, methodsMapping.REPORT__ORDER);
},
/**
* This method is used to return a list of sales around a subject property PLUS offer a series of statistics based on the response results.
*
* @param {Object} payload - The payload object containing required info.
* @param {String} payload.sa_property_id - Unique ID of the property
* @param {String} payload.mm_fips_state_code - State FIPS of the property
* @param {Number} [payload.date_transfer(+/-)] - Sold
* @param {Number} [payload.distance_in_km] - Distance (in kilometers)
* @param {Number} [payload.nbr_bath(+/-)] - Baths
* @param {Number} [payload.nbr_bedrms(+/-)] - Beds
* @param {Number} [payload.sqft(+/-)] - SQFT
* @param {Number} [payload.yr_blt(+/-)] - Year built
*
* @example
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* var payload = {
* "sa_property_id": "0039025849",
* "mm_fips_state_code": "06",
* "date_transfer(+/-)": 12,
* "distance_in_km": 1.6,
* "nbr_bath(+/-)": 1,
* "nbr_bedrms(+/-)": 1,
* "sqft(+/-)": 0.2,
* "yr_blt(+/-)": 20
* };
*
* ttb.propertyComps(payload)
* .then(function(res) {
* if (res.response.status === 'OK') {
* // your success code here to consume res.response.data
* console.log(res.response.data);
* } else {
* // your failure code here to consume res.response.data
* console.log(res.response.data);
* }
* }, function(err) {
* // your failure code here
* })
* .always(function() {
* // your on-complete code here as common for both success and failure
* });
*
* @return {Object} promise - Jquery AJAX deferred promise is returned which on-success returns the required info.
* */
propertyComps: function (payload) {
var request = {
method: 'POST',
data: JSON.stringify(payload)
};
return this._ajax(request, methodsMapping.PROPERTY__COMPS);
},
/**
* This method is used to pull details of a property specified by property_id
*
* @param {Object} payload - The payload object containing required info.
* @param {Number} payload.property_id - Unique ID of the property
* @param {Number} payload.state_fips - State FIPS of the property
*
* @param {Object} [options] - The options object
* @param {String} [options.autoFillContext] - A query selector of an element(s) inside which to look for inputs elements
* having <code>data-ttb-field</code>attribute.
* @param {Boolean} [options.autoFillClearExisting="false"] - Clear the existing / previously added values to all of the fields that are to be auto-filled.
* @param {Number} [options.autoFillDelay="0"] - A delay in milliseconds, useful when wished to visualize auto filling. (50 is a good value to test)
*
* @example
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* var payload = {
* property_id: 0091683346
* state_fips: 25,
* };
*
* var options = {
* autoFillContext: '#propertyDetails__form'
* };
*
* ttb.propertyDetails(payload, options)
* .then(function(res) {
* if (res.response.status === 'OK') {
* // your success code here to consume res.response.data for any extra effort other than the auto-fill
* console.log(res.response.data);
* } else {
* // your failure code here to consume res.response.data
* console.log(res.response.data);
* }
* }, function(err) {
* // your failure code here
* })
* .always(function() {
* // your on-complete code here as common for both success and failure
* });
*
* @return {Object} promise - Jquery AJAX deferred promise is returned which on-success returns the required info.
* */
propertyDetails: function (payload, options) {
var _self;
_self = this;
options = options || {autoFillContext: null};
var request = {
method: 'POST',
data: JSON.stringify(payload)
};
return this._ajax(request, methodsMapping.PROPERTY__DETAILS)
.then(function (res) {
options.autoFillContext && _self._fillFields(options.autoFillContext, res.response.data, options.autoFillClearExisting, options.autoFillDelay);
return res;
});
},
/**
* This method is used to check for the status on phone and/or email fields order for the properties of the given farm.
* on successful call, check for <code>data.phone_status</code> and <code>data.email_status</code> flags.
* value "completed" means that the requested contact field is ready, and so .getFarm() should be called again to fetch the farm.
* "ordered" means the order is made, but it is in progress on the backend. orders usually takes up to ~2 minutes.
* <br><br>
* Note: The farm is supposed to be ordered/made via .globalSearch() method.
*
* @param {String} farmId - The <code>farm_id</code> of the target farm to be checked.
*
* @example
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* var farmId = 123;
*
* ttb.checkPEFarmStatus(farmId)
* .then(function(res) {
* if (res.response.status === 'OK') {
* // your success code here to consume res.response.data
* console.log(res.response.data);
* } else {
* // your failure code here to consume res.response.data
* console.log(res.response.data);
* }
* }, function(err) {
* // your failure code here
* })
* .always(function() {
* // your on-complete code here as common for both success and failure
* });
*
* @return {Object} promise - Jquery AJAX deferred promise is returned which on-success returns the required info.
* */
checkPEFarmStatus: function (farmId) {
var url = (this.baseURL + methodsMapping.FARM__PE__CHECK_STATUS.endpoint).replace('{{farmId}}', farmId);
var request = {
method: 'GET',
url: url
};
return this._ajax(request, methodsMapping.FARM__PE__CHECK_STATUS);
},
/**
* This method is used to fetch the given farm. i.e. to fetch all the properties/records of the given farm.
* <br><br>
* Note: The farm is supposed to be ordered/made via .globalSearch() method.
*
* @param {String} farmId - The <code>farm_id</code> of the target farm to be fetched.
*
* @example
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* var farmId = 123;
*
* ttb.getFarmProperties(farmId)
* .then(function(res) {
* if (res.response.status === 'OK') {
* // your success code here to consume res.response.data
* console.log(res.response.data);
* } else {
* // your failure code here to consume res.response.data
* console.log(res.response.data);
* }
* }, function(err) {
* // your failure code here
* })
* .always(function() {
* // your on-complete code here as common for both success and failure
* });
*
* @return {Object} promise - Jquery AJAX deferred promise is returned which on-success returns the required info.
* */
getFarmProperties: function (farmId) {
var url = (this.baseURL + methodsMapping.FARM__GET_DETAILS.endpoint).replace('{{farmId}}', farmId);
var request = {
method: 'GET',
url: url
};
return this._ajax(request, methodsMapping.FARM__GET_DETAILS);
},
/**
* This method is used to fetch the list of all the farms that were bought by the user.
* <br><br>
* Note: The farm is supposed to be ordered/made via .globalSearch() method.
*
* @example
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* ttb.getFarmsList()
* .then(function(res) {
* if (res.response.status === 'OK') {
* // your success code here to consume res.response.data
* console.log(res.response.data);
* } else {
* // your failure code here to consume res.response.data
* console.log(res.response.data);
* }
* }, function(err) {
* // your failure code here
* })
* .always(function() {
* // your on-complete code here as common for both success and failure
* });
*
* @return {Object} promise - Jquery AJAX deferred promise is returned which on-success returns the required info.
* */
getFarmsList: function () {
var request = {
method: 'GET'
};
return this._ajax(request, methodsMapping.FARM__GET_LIST);
},
/**
* This method is used to search all properties matching a set of criteria.<br>
* There is a vast number of criteria available, see the Available Search Fields and Search Criteria section.
*
* @param {Object} payload - The payload object containing required info.
* @param {String} payload.mm_fips_state_code - State FIPS of the property
* @param {Object | String} [payload.FIELD_NAME] - Other search fields to be sent.
* @param {Object} [payload.customFilters] - Filters fields are to be sent via this wrapper - See the available search fields for more.
* @param {Object} [payload.customFilters.is_site_number_even_search] - A custom filter
* @param {Object} [payload.customFilters.FIELD_NAME] - Other filter type search fields to be sent.
* @param {Object} [payload.searchOptions] - Additional options to take control on records.
* @param {String} [payload.searchOptions.max_limit] - Limit the matched records.
* @param {String} [payload.searchOptions.omit_saved_records=false] - Suppress/Omit records already saved.
*
* @param {Object} [queryParams] - The query string params limit and page on URL are used to control pagination of the result.
* @param {String} [queryParams.limit] - Determines how many recs to include in one page.
* @param {String} [queryParams.page] - Specifies the page number in the full result set.
*
* @example
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* var queryParams = { limit: 1000, page: 2 };
* var payload = {
* "mm_fips_state_code": "06", // State FIPS
* "mm_fips_muni_code": "059", // County FIPS
* "sa_site_city": [ // Cities
* "ANAHEIM"
* ],
* "sa_site_zip": [ // Zip Codes
* "92801",
* "92805"
* ],
* "sa_site_mail_same": "Y",
* "sa_owner_1_type": "0",
* "sa_nbr_bedrms": { // Beds
* "match": "<=",
* "value": 3
* },
* "sa_nbr_bath": { // Baths
* "match": "<=",
* "value": 2
* },
* "use_code_std": [
* "RSFR",
* "RCON"
* ],
* "sa_yr_blt": { // Year built
* "match": "From-To",
* "value": {
* "from": 1950,
* "to": 2002
* }
* },
* "sa_assr_year": {
* "match": ">",
* "value": 2000
* },
* "searchOptions": { // Additional Search Options
* "omit_saved_records": false
* },
* "customFilters": { // Filters
* "is_site_number_even_search": "Y"
* }
* };
*
* ttb.globalSearch(payload, queryParams)
* .then(function(res) {
* if (res.response.status === 'OK') {
* // your success code here to consume res.response.data
* console.log(res.response.data);
* } else {
* // your failure code here to consume res.response.data
* console.log(res.response.data);
* }
* }, function(err) {
* // your failure code here
* })
* .always(function() {
* // your on-complete code here as common for both success and failure
* });
*
* @return {Object} promise - Jquery AJAX deferred promise is returned which on-success returns the required info.
* */
globalSearch: function (payload, queryParams) {
var request = {
method: 'POST',
data: JSON.stringify(payload)
};
return this._ajax(request, methodsMapping.GLOBAL_SEARCH__PROPERTIES, queryParams);
},
/**
* This method is to only get the count (as opposed to full set of records) against a certain set of search criteria.<br>
* Note - It accepts the same search criteria input as for [global_search]{@link TTB#globalSearch} API.
*
* @param {Object} payload - The payload object containing required info.
*
* @example
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* var payload = {
* "mm_fips_state_code": "06", // State FIPS
* "mm_fips_muni_code": "059", // County FIPS
* "sa_site_city": [ // Cities
* "ANAHEIM"
* ],
* "sa_site_zip": [ // Zip Codes
* "92801",
* "92805"
* ],
* "sa_nbr_bedrms": { // Beds
* "match": "<=",
* "value": 3
* },
* "searchOptions": { // Additional Search Options
* "omit_saved_records": false
* },
* "customFilters": { // Filters
* "is_site_number_even_search": "Y"
* }
* };
*
* ttb.globalSearchCount(payload, params)
* .then(function(res) {
* if (res.response.status === 'OK') {
* // your success code here to consume res.response.data
* console.log(res.response.data);
* } else {
* // your failure code here to consume res.response.data
* console.log(res.response.data);
* }
* }, function(err) {
* // your failure code here
* })
* .always(function() {
* // your on-complete code here as common for both success and failure
* });
*
* @return {Object} promise - Jquery AJAX deferred promise is returned which on-success returns the required info.
*
* */
globalSearchCount: function (payload) {
var request = {
method: 'POST',
data: JSON.stringify(payload)
};
return this._ajax(request, methodsMapping.GLOBAL_SEARCH__COUNT);
},
/**
* This method is to get all the properties nearby the given geolocation against the given radius.<br>
* Note - for payloadExtension, It accepts the same search criteria format input as for [global_search]{@link TTB#globalSearch} API.
*
* @param {Object} payload - The payload object containing required info.
* @param {Object} [payloadExtension] - An optional payload to be merge into the auto-generated one. It can be utilized to
* add custom filters like "Empty Nester" and/or other useful fields.
*
* @example
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* var payload = {
* mm_fips_state_code: "06", // State FIPS
* center_lat: "33.97652", // sa_y_coord // to be used in a geometry of type "circle" for radius.
* center_lng: "-117.726299", // sa_x_coord // required for same above reason.
* radius: "1", // required for same above reason.
* limit: "20" // Optional.
* };
*
* ttb.nearbySearch(payload, params)
* .then(function(res) {
* if (res.response.status === 'OK') {
* // your success code here to consume res.response.data
* console.log(res.response.data);
* } else {
* // your failure code here to consume res.response.data
* console.log(res.response.data);
* }
* }, function(err) {
* // your failure code here
* })
* .always(function() {
* // your on-complete code here as common for both success and failure
* });
*
* @return {Object} promise - Jquery AJAX deferred promise is returned which on-success returns the required info.
*
* */
nearbySearch: function (payload, payloadExtension) {
var queryParams = {limit: payload.limit || 1000, page: 1};
var finalPayload = {
"mm_fips_state_code": payload.mm_fips_state_code, // State FIPS
// default - with type Single Family Residence, and Condonium
"use_code_std": ["RSFR", "RCON"],
// default - no filters
customFilters: {},
// default - no searchOptions
searchOptions: {
"max_limit": payload.limit || undefined
},
// nearby search is processed by using radius geometry with the main globalSearchMethod
"geometry": {
"match": "circle",
"value": {
"center_lat": payload.center_lat, // sa_y_coord
"center_lng": payload.center_lng, // sa_x_coord
"radius": payload.radius
}
}
};
// Include any other extension to payload if provided. e.g. customFilters or other fields criteria.
finalPayload = $.extend(finalPayload, payloadExtension);
var request = {
method: 'POST',
data: JSON.stringify(finalPayload)
};
return this._ajax(request, methodsMapping.GLOBAL_SEARCH__PROPERTIES, queryParams);
},
/**
* This method will allow you to verify what reports are available to your user profile.
*
* @example
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* ttb.getTypesReport()
* .then(function(res) {
* if (res.response.status === 'OK') {
* // your success code here to consume res.response.data
* console.log(res.response.data);
* } else {
* // your failure code here to consume res.response.data
* console.log(res.response.data);
* }
* }, function(err) {
* // your failure code here
* })
* .always(function() {
* // your on-complete code here as common for both success and failure
* });
*
*
* @return {Object} promise - Jquery AJAX deferred promise is returned which on-success returns the required info.
*
* */
getTypesReport: function () {
var request = {
method: 'GET'
};
return this._ajax(request, methodsMapping.REPORT__GET_TYPES);
},
/**
* This method provides the complete list of all fields that can be used to construct search terms for
* [global_search]{@link TTB#globalSearch} and [global_search_count]{@link TTB#globalSearchCount} APIs. <br><br>
*
* To view the complete list of all available search fields and their possible values. <br>
* Please follow this [JSON presentation]{@link http://jsoneditoronline.org/?id=ba6b41ee73822c653dae0e2cc8cf6351} -
* The key info you should look for is the <code>field_name</code>, <code>search_type</code> and <code>choices</code>.
*
* @example
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* ttb.getSearchFields()
* .then(function(res) {
* if (res instanceof Array) {
* // your success code here to consume res as fields list. see example [< JSON here >]{@link http://jsoneditoronline.org/?id=ba6b41ee73822c653dae0e2cc8cf6351}
* console.log(res);
* } else {
* // your failure code here to consume res
* console.log(res);
* }
* }, function(err) {
* // your failure code here
* })
* .always(function() {
* // your on-complete code here as common for both success and failure
* });
*
* @return {Object} promise - Jquery AJAX deferred promise is returned which on-success returns the required info.
*
* */
getSearchFields: function () {
var request = {
method: 'GET'
};
return this._ajax(request, methodsMapping.GLOBAL_SEARCH__FIELDS);
},
/**
* @description
* This method saves the sponsor selection performed by the user with given credentials.
* Performs an optional login identical to existing method loginRemote()
*
* @param {Object} payload - The payload object containing required info.
* @param {String} payload.stk - pls check loginRemote() for more.
* @param {String} [payload.getuser_url] - pls check loginRemote() for more.
*
* @param {Boolean} [performLogin=false] - To perform a login - identical to loginRemote(),
*
* @example
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* var payload = {
* stk: "xxxxxxxxxxxxxxx",
* getuser_url: 'https://www.yoursite.com/webservices/getuser.json' // absolute URL to the API
* };
*
* var performLogin = true;
*
* ttb.saveSponsorSelection(payload, performLogin)
* .then(function(res) {
* if (res.response.status === 'OK') {
* // your success code here to consume res.response.data
* console.log(res.response.data);
* } else {
* // your failure code here to consume res.response.data
* console.log(res.response.data);
* }
* }, function(err) {
* // your failure code here
* })
* .always(function() {
* // your on-complete code here as common for both success and failure
* });
*
* @return {Object} promise - Jquery AJAX deferred promise is returned which on-success returns the required info.
*
* */
saveSponsorSelection: function (payload, performLogin) {
var request = {
method: 'POST',
data: JSON.stringify(payload)
};
var queryParams = {
save_and_login: !!performLogin
};
return this._ajax(request, methodsMapping.SPONSOR__SAVE_SELECTION, queryParams);
},
/**
* @description
* This method deactivate the sponsor selection previously performed by the user with given credentials.
*
* @param {Object} payload - The payload object containing required info.
* @param {String} payload.email - The email address of the user.
*
* @example
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* var payload = {
* email: "awesomeuser99@domain.com"
* };
*
* ttb.clearSponsorSelection(payload)
* .then(function(res) {
* if (res.response.status === 'OK') {
* // your success code here to consume res.response.data
* console.log(res.response.data);
* } else {
* // your failure code here to consume res.response.data
* console.log(res.response.data);
* }
* }, function(err) {
* // your failure code here
* })
* .always(function() {
* // your on-complete code here as common for both success and failure
* });
*
* @return {Object} promise - Jquery AJAX deferred promise is returned which on-success returns the required info.
*
* */
clearSponsorSelection: function (payload) {
var request = {
method: 'POST',
data: JSON.stringify(payload)
};
//var queryParams = {
// perform_logout: !!performLogout
//};
return this._ajax(request, methodsMapping.SPONSOR__CLEAR_SELECTION);
},
/**
* This method opens up a Net Sheet modal against target property. The modal contains an iframe to TTB's official Net Sheet version.
* <br>
* This same method is being used inside from instantLookupWidget() for the "Net Sheet" Action.
* @param {string} propertyId - The "sa_property_id" field value from the target property object. This exists in each
* property object retrieved either from global search, search by address/owner/APN, nearbySearch, etc.
* @param {string} propertyFullAddress - The full address to be shown inside Net Sheet.
* This can be build using static util method TTB.utilBuildFullAddress(), for more check method's documentation.
*
* @example
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* var selectedProperty = { ... }; // can come from whatever your feature logic is.
*
* // build parameters.
* var propertyId = selectedProperty.sa_property_id;
* var propertyFullAddress = TTB.utilBuildFullAddress(selectedProperty);
*
* // open net sheet against the target property.
* ttb.openNetSheet(propertyId, propertyFullAddress);
*
* @return {String} $modal - A JQuery reference to the modal DOMNode Element, of opened Net Sheet.
*
* */
openNetSheet: function (propertyId, propertyFullAddress) {
var modalOptions, iframeOptions, ttbExportAppBaseURL;
modalOptions = {
id: 'ttb-sdk--net-sheet--modal',
title: 'Net Sheet'
};
// export-app baseURL - dynamic - dev vs prod. - check inside to serve only prod url.
ttbExportAppBaseURL = window.TTB.getTTBExportAppBaseURL();
iframeOptions = {
id: 'ttb-sdk--net-sheet--iframe',
height: '635px',
origin: ttbExportAppBaseURL,
pathname: '/netsheet',
params: {
partnerKey: this.config.partnerKey,
verticalName: this.sponsor.name,
verticalTitle: this.sponsor.title,
propertyId: propertyId,
propertyAddress: propertyFullAddress,
debug: this.debug,
TTBSID: TTB._getLocal(defaults.sessionKeyName)
}
};
this._log(['openNetSheet : options built :', iframeOptions, modalOptions]);
// return the reference of the modal instance.
return window.TTB.utilIframeModal(modalOptions, iframeOptions);
},
/**
* This method builds the address payload using the google <code>autocomplete</code> instance once it's
* <code>"place_changed"</code> event fires.
* The returned payload can be utilized to consume <code>searchBySiteAddress()</code> API or you can fill form fields using SDK's autoFill API.
*
* @param {object} autocomplete - The google autocomplete instance used on your site, consuming it's <code>"place_changed"</code> event.
*
* @param {Object} [options]- The options object
* @param {String} [options.autoFillContext] - A query selector of an element(s) inside which to look for inputs elements
* having <code>data-ttb-field</code> attribute.
* - For example: <input type="text" <code>data-ttb-field="site_address"</code> />
* @param {Boolean} [options.autoFillClearExisting="false"] - Clear the existing / previously added values to all of the fields that are to be auto-filled.
* @param {Number} [options.autoFillDelay="0"] - A delay in milliseconds, useful when wished to visualize auto filling. (50 is a good value to test)
*
* @example
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* // render your autocomplete component instance on your desired location
* var autocompleteElement = document.getElementById('googleBuildAddress__autocomplete');
* var autocomplete = new google.maps.places.Autocomplete(autocompleteElement, {types: ['geocode']});
*
* // when the user selects an address from the drop-down, populate the address fields in the form.
* autocomplete.addListener('place_changed', function () {
*
* // approach # 01 - auto-fill only - build up the address by auto-filling the form fields and leaves the submission logic.
* // <i>(Note: before submission, make sure you build <code>site_address</code> using <code>site_street_number</code> + ' ' + <code>site_route</code>)</> </i>
*
* var options = {
* autoFillContext: '#searchBySiteAddress__form'
* };
* ttb.googleBuildAddress(autocomplete, options);
*
* //--- approach # 01 - auto-fill only - ends ---
*
* // -- OR --
*
* // approach # 02 - direct submission - build up the address by getting the payload and proceed with <code>searchBySiteAddress()</code> to retrieve the result.
* var payload = ttb.googleBuildAddress(autocomplete);
* ttb.searchBySiteAddress(payload)
* .then(function(res) {
* if (res.response.status === 'OK') {
* // your success code here to consume res.response.data for any extra effort other than the auto-fill
* console.log(res.response.data);
* } else {
* // your failure code here to consume res.response.data
* console.log(res.response.data);
* }
* }, function(err) {
* // your failure code here
* })
* .always(function() {
* // your on-complete code here as common for both success and failure
* });
* // --- approach # 02 - direct submission - ends ---
*
* });
*
*
* @return {Object} address built address payload object using google place components, having following fields against mentioned mapping.
*
* @return {Object} address.site_street_number Component Type: <code>"street_number"</code> | Name Type: <code>"short_name"</code>.
* @return {Object} address.site_route Component Type: <code>"route"</code> | Name Type: <code>"short_name"</code>.
* @return {Object} address.site_address *Built using <code>site_street_number</code> + ' ' + <code>site_route</code>.
* @return {Object} address.site_city Component Type: <code>"locality"</code> | Name Type: <code>"long_name"</code>.
* @return {Object} address.site_neighborhood Component Type: <code>"neighborhood"</code> | Name Type: <code>"long_name"</code>.
* @return {Object} address.site_state Component Type: <code>"administrative_area_level_1"</code> | Name Type: <code>"short_name"</code>.
* @return {Object} address.site_zip Component Type: <code>"postal_code"</code> | Name Type: <code>"short_name"</code>.
* @return {Object} address.county Component Type: <code>"administrative_area_level_2"</code> | Name Type: <code>"short_name"</code>.
* @return {Object} address.country Component Type: <code>"country"</code> | Name Type: <code>"long_name"</code>.
*
* */
googleBuildAddress: function (autocomplete, options) {
var place, addressInfo, addressComp, addressType, addressValue, componentForm;
// our details object can be used for payload
addressInfo = {};
// place-components vs form-fields mapping
componentForm = {
street_number: {field_name: 'site_street_number', name_type: 'short_name'},
route: {field_name: 'site_route', name_type: 'short_name'},
locality: {field_name: 'site_city', name_type: 'long_name'},
neighborhood: {field_name: 'site_neighborhood', name_type: 'long_name'},
administrative_area_level_1: {field_name: 'site_state', name_type: 'short_name'},
administrative_area_level_2: {field_name: 'site_county', name_type: 'short_name'},
postal_code: {field_name: 'site_zip', name_type: 'short_name'}
//country: {field_name: 'country', name_type: 'long_name'}
};
// get the current place info from google autocomplete instance
place = autocomplete.getPlace();
// iterate over each component available in selected place
for (var i = 0, len = place.address_components.length; i < len; i++) {
addressComp = place.address_components[i];
addressType = addressComp.types[0];
// check if the component is of our use e.g. "administrative_area_level_1"
if (componentForm[addressType]) {
addressValue = addressComp[componentForm[addressType].name_type];
// fill the address info object
addressInfo[componentForm[addressType].field_name] = addressValue;
// check to auto-fill field if auto-fill-context option was provided.
options && options.autoFillContext && this._fillField(options.autoFillContext, componentForm[addressType].field_name, addressValue);
}
}
// Special field handling for "site_address"
// add field only if at least one of the field is given.
if (addressInfo.site_street_number || addressInfo.site_route) {
addressInfo.site_address = (addressInfo.site_street_number || '') +
(addressInfo.site_street_number && addressInfo.site_route ? ' ' : '') +
(addressInfo.site_route || '');
}
// replace "County" suffix by google for county for some addresses.
// Handle missing county field for some addresses
addressInfo.site_county = addressInfo.site_county ? addressInfo.site_county.replace(' County', '') : undefined;
//addressInfo.site_address = addressInfo.site_street_number + ' ' + addressInfo.site_route;
//addressInfo.custom_google_formatted_address = place.formatted_address;
// return the built address info, in order to let consumer use the info the way they want.
return addressInfo;
},
/**
* This method renders a widget includes google autocomplete and supported actions drop-down. <br>
* Make sure <strong>Google Maps script</strong> file is injected and <code>ttb.instantLookupWidget()</code> should be called inside global <code>googleInit()</code> function. <br>
* And also <strong>ttbSdk.min.css</strong> file is injected for proper style and look for the widgets.
*
* @param {String} elementSelector - DOM element selector where the widget needs to be rendered.
* <code>#lorem</code> or <code>.ipsum</code> etc.
*
* @param {Object} [actions] - An object contains mapping callbacks to be consumed when any action is clicked.
* @param {Function} [actions.fullProfileReport] - To be invoked with an info object (info.success, info.data) as argument, when user selects an address
* from the autocomplete and then clicks the action "Full Profile Report". This info can be used for handling success and failure.
*
* @param {Object} [visibility] - An object contains mapping of actions mentioning their visibility.
* @param {Function} [visibility.fullProfileReport=true] - A flag to show/visible the "Full Report Profile" action.
* (By Default, its shown. To hide it, just pass it with value as false)
* @param {Function} [visibility.netSheet=true] - A flag to show/visible the "Net Sheet" action.
*
* @example
*
* // with basic and minimum requirement.
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* // define googleInit() if it is not already created.
* window.googleInit = function () {
*
* var elementSelector = '#ttb-instant-lookup-wrapper';
* var $instantLookup = ttb.instantLookupWidget(elementSelector);
* };
*
* @example
*
* // Sample to suppress/hide a certain action, by using optional visibility param.
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* // define googleInit() if it is not already created.
* window.googleInit = function () {
*
* var visibility = {
* fullProfileReport: false, // to suppress this action
* netSheet: true, // if value is same as its default visibility. then it is equivalent to just not passing it.
* };
*
* var elementSelector = '#ttb-instant-lookup-wrapper';
* var $instantLookup = ttb.instantLookupWidget(elementSelector, undefined, visibility);
* };
*
* @example
*
* // with advanced configuration for handling success, and failure of the actions results.
* // this sample can be joined to above "visibility" param example.
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* // define googleInit() if it is not already created.
* window.googleInit = function () {
*
* var actions = {
* fullProfileReport: function(info) {
* if (info.success === 'OK') {
* // your success code here to consume info.data
* console.log(info.data);
* } else {
* // your failure code here to consume info.data
* console.log(info.data);
* }
* }
* };
*
* var elementSelector = '#ttb-instant-lookup-wrapper';
* var $instantLookup = ttb.instantLookupWidget(elementSelector, actions);
* };
*
* @return {Object} $element - JQuery reference to the rendered widget container element.
*
* */
instantLookupWidget: function (elementSelector, actions, visibility) {
var o, autoComplete, _self, refs;
_self = this;
actions = actions || {};
refs = {};
o = {};
o.tableOptions = {
containerSelector: '.ttb-sdk--instant-lookup--list',
onInit: undefined, // handled bellow passing required references.
onSelect: undefined, // handled bellow passing required references.
columns: [
{
label: 'Address',
fieldName: 'customAddress'
}, {
label: 'Unit',
fieldName: 'v_unit'
}, {
label: 'Apn',
fieldName: 'sa_parcel_nbr_primary'
}, {
label: 'City',
fieldName: 'customCity'
}, {
label: 'Zip',
fieldName: 'sa_site_zip'
}, {
label: 'Mail State',
fieldName: 'sa_mail_state'
}, {
label: 'Owner name',
fieldName: 'formatted_sa_owner_1'
}
]
};
/* allowed actions - starts -
check visibility and build the allowed actions list */
o.allowedActionsMarkup = [];
(function () {
var allActions;
visibility = visibility || {};
allActions = [
{name: 'netSheet', label: 'Net Sheet', defaultVisibility: true},
{name: 'fullProfileReport', label: 'Full Profile Report', defaultVisibility: true},
// {name: 'generateReport', label: 'Generate Report', defaultVisibility: true},
];
allActions.forEach(function (action) {
// build each action visibility. first look into visibility param, if not provided then use default visibility of the action.
visibility[action.name] = visibility[action.name] !== undefined ? visibility[action.name] : action.defaultVisibility;
// if action to be made visible - build markup and add it into allowed actions markup list.
if (visibility[action.name]) {
o.allowedActionsMarkup.push(
' <li><a data-action-name="{{name}}" href="javascript:">{{label}}</a></li>'
.replace('{{name}}', action.name)
.replace('{{label}}', action.label)
);
}
});
// flatten the list into one string
o.allowedActionsMarkup = o.allowedActionsMarkup.join('');
// _self._log(['instantLookupWidget: allowedActionsMarkup:', visibility, o.allowedActionsMarkup]);
})();
/* allowed actions - ends */
o.selectedAction = window.TTB._getLocal('selectedAction', o.selectedAction) || {
name: 'fullProfileReport',
};
o.widgetClass = 'ttb-sdk--instant-lookup--container';
o.widgetTemplate = [
'<!-- the wait spinner box -->',
'<div class="col-xs-12">',
' <i class="ttb-sdk--spinner"></i>',
'</div>',
'<!-- the google autocomplete address lookup -->',
'<div id="ttb-sdk--instant-lookup--address" class="col-xs-12 col-md-7">',
' <div class="">1 - Type in and select the property address below</div>',
' <div class="ttb-sdk--instant-lookup--auto-complete-wrapper">',
' <input type="text" class="form-control" id="ttb-sdk--instant-lookup--auto-complete" name="ttb-sdk--instant-lookup--auto-complete" placeholder="Search for an address...">',
' </div>',
' <i class="ttb-sdk--icon--right-arrow"></i>',
' <div class="ttb-sdk--instant-lookup--footer">',
' (If this is a Condo, APT, or other Multi Unit, please do not enter the unit #. It will ask you for it after you enter the address.)</div>',
' </div>',
'</div>',
'<!-- actions menu -->',
'<div id="ttb-sdk--instant-lookup--actions" class="col-xs-12 col-md-5">',
' <div class="">2 - Select the type of report you want below</div>',
' <!-- split button -->',
' <div class="btn-group col-xs-12">',
' <ul> {{allowedActionsMarkup}} </ul>',
' </div>',
'</div>',
'<!-- help tip ! -->',
'<div id="ttb-sdk--instant-lookup--alert" class="col-xs-12 text-center">',
' <div class="alert alert-warning" style="border: 1px solid;">',
' Looks like you have not allowed popups for our site, yet. You can click <a href="javascript:" target="_blank">here</a> to get your report.',
' </div>',
'</div>',
'<!-- multiple matched properties list - [conditional] ! -->',
'<div class="col-xs-12 ttb-sdk--instant-lookup--list">',
'</div>',
'<!-- widget footer ! -->',
'<div class="col-xs-12 ttb-sdk--instant-lookup--footer text-center">',
' <i class="ttb-sdk--icon--info"></i>',
' Your report will automatically be created and displayed for you.',
'</div>'
].join('')
// .replace('{{selectedActionLabel}}', o.selectedAction.label);
.replace('{{allowedActionsMarkup}}', o.allowedActionsMarkup);
o.$container = $(elementSelector);
// validate if target element not found
if (!o.$container.length) {
this._log(['instantLookupWidget : abort : element not found - ', elementSelector]);
return null;
}
// add required class for CSS
o.$container
.addClass(o.widgetClass)
// render the widget template
.append(o.widgetTemplate);
// for scope bootstrap instances.
if (window.TTB.scopedBootstrap) {
o.$container
.addClass(defaults.classScopedBootstrap)
.addClass(defaults.classScopedBootstrapBody);
}
// capture the multiple match view container
o.$multipleMatchContainer = $(o.tableOptions.containerSelector);
// load data-table library in advance.
window.TTB.utilLoadDataTable();
// check for google autocomplete first
try {
var test = google.maps.places.Autocomplete;
} catch(e) {
this._log([
'instantLookupWidget : abort : "google.maps.places.Autocomplete" not found -',
' please make sure the google script was loaded and that instantLookupWidget() is being called inside/after google load cb is ',
' called i.e. googleInit() or the mentioned callback=* in the google script src value.'
]);
// abort the rendering
return null;
}
// bind google autocomplete
autoComplete = {};
autoComplete.$element = o.$container.find('#ttb-sdk--instant-lookup--auto-complete');
autoComplete.instance = new google.maps.places.Autocomplete(autoComplete.$element[0], {types: ['geocode']});
//TODO destroy listener when element widget gets destroyed.
// on address select, store the address components for later use when action is clicked.
autoComplete.instance.addListener('place_changed', function() {
_self._log(['instantLookupWidget: place_changed called.']);
// reset previous attempt results
resetSuccessAlert();
// reset any prev attempt to multiple match list view
resetMultipleMatchListView();
// fill the address form fields
o.selectedAddressInfo = _self.googleBuildAddress(autoComplete.instance);
});
o.$actions = o.$container.find('#ttb-sdk--instant-lookup--actions li');
// bind click handlers to all actions
o.$container.find('#ttb-sdk--instant-lookup--actions ul').on('click', 'li', setAndInvokeAction);
// reset success alert bar used for e.g. full profile report link.
function resetSuccessAlert() {
//_self._log(['instantLookupWidget: resetSuccessAlert called.']);
$('#ttb-sdk--instant-lookup--alert')
.hide()
.find('a')
.attr('href', 'javascript:');
}
// resets the list rendered against any previously searched address
function resetMultipleMatchListView() {
_self._log(['instantLookupWidget: resetMultipleMatchListView called.']);
// clear data-table instance (if was rendered)
refs.dataTable && refs.dataTable.destroy(true);
refs.dataTable = null;
// clear DOM completely (if any thing still left by prev data-table instance)
o.$multipleMatchContainer.empty();
}
// to be called from dropdown, selects the action, and auto-invokes it.
function setAndInvokeAction(evt) {
//console.log('setAndInvokeAction:');
setActionSelection(evt);
invokeSelectedAction();
}
// sets the clicked dropdown-action as selected.
function setActionSelection(evt) {
//console.log('setActionSelection:', evt);
o.selectedAction.name = $(evt.target).data('action-name');
// o.selectedAction.label = $(evt.target).text();
// o.$selectedAction.text(o.selectedAction.label || '');
window.TTB._setLocal('selectedAction', o.selectedAction);
}
// to be called on click of selected action button, or any other dropdown action.
function invokeSelectedAction() {
var promise, disableControls, enableControls, handleError;
//console.log('invokeSelectedAction');
// reset previous attempt results
resetSuccessAlert();
// reset any prev attempt to multiple match list view
resetMultipleMatchListView();
// if no address was selected / fetched via autocomplete
if (!o.selectedAddressInfo) {
autoComplete.$element.focus();
return;
}
// to disable widget controls as activating wait state
disableControls = function() {
// activate wait spinner
o.$container.find('.ttb-sdk--spinner').addClass('active');
// disable widget controls
autoComplete.$element.prop('disabled', true);
o.$actions.addClass('ttb-sdk--disabled-link');
};
// function to enable widget controls back to normal
enableControls = function () {
// deactivate wait spinner
o.$container.find('.ttb-sdk--spinner').removeClass('active');
// enable widget controls
autoComplete.$element.prop('disabled', false);
o.$actions.removeClass('ttb-sdk--disabled-link');
};
// common handler for error relates scenarios. 3 scenarios.
handleError = function (message) {
_self._log(['instantLookupWidget : searchBySiteAddress - handleError - ', message]);
enableControls();
alert('TTB - Lookup Failed. \nReason: ' + message);
selectionActionCb(false, {
message: message
});
};
// activate wait state
disableControls();
// get property_id and state info against the given addressInfo
promise = _self.searchBySiteAddress(o.selectedAddressInfo);
promise
.then(function (res) {
var property, records;
_self._log(['instantLookupWidget : searchBySiteAddress - complete - ', res]);
res = res.response;
// error - when no records found
if (res.status !== 'OK' || !res.data || !res.data.length) {
handleError(res.data ? res.data[0] : 'Failed in property lookup.');
return;
}
// error - when multiple records found.
if (res.data.length > 1) {
//handleError('Multiple records found, please enter unit number. For example: 303');
//unitNumber = prompt('Multiple records found. If it\'s a Condo or Apt. complex, please provide a specific unit number.');
//
//if (unitNumber) {
// o.selectedAddressInfo.site_unit = unitNumber;
// invokeSelectedAction();
//} else {
// handleError('Couldn\'t find the property.');
//}
// renders the retrieved list
//var records = window.TTB._getLocal('ilookup-records');
records = res.data.map(function (record) {
record.customAddress = record.sa_site_house_nbr + ' ' + TTB.utilCapitalize(record.sa_site_street_name);
record.customCity = TTB.utilCapitalize(record.sa_site_city);
return record;
});
// capture the instance reference, and stop the wait state/
o.tableOptions.onInit = function (dataTable) {
refs.datatable = dataTable;
enableControls();
};
// proceed with the used action while searching.
o.tableOptions.onSelect = function (property) {
// activate wait state
disableControls();
proceedActionWithTargetProperty(property, enableControls);
};
window.TTB.utilRenderTable(records, o.tableOptions);
_self._log(['instantLookupWidget : match count:', records.length]);
return;
}
// targeted property found, go ahead to invoke the selected action.
property = res.data[0];
proceedActionWithTargetProperty(property, enableControls);
}, function() {
handleError(defaults.errorMessages.GENERAL__CONNECT_FAILED + '.');
});
}
// proceed with target action, after property is identified with any of the supported methods above.
function proceedActionWithTargetProperty(property, enableControls) {
//property.custom_google_formatted_address = o.selectedAddressInfo.custom_google_formatted_address;
// generate formatted full address
property.customFullAddress = window.TTB.utilBuildFullAddress(property);
switch (o.selectedAction.name) {
case 'netSheet':
_self._log(['instantLookupWidget : selectedAction: netSheet']);
actionOpenNetSheet(property, enableControls);
break;
//case 'generateReport':
// ttb._log(['instantLookupWidget : selectedAction: generateReport - dev in progress.']);
// enableControls();
// break;
case 'fullProfileReport':
_self._log(['instantLookupWidget : selectedAction: fullProfileReport']);
actionOrderReport(property, enableControls);
break;
default:
_self._log(['instantLookupWidget : selectedAction: action not found - ', elementSelector]);
enableControls();
}
}
// invokes the selected action with given promise
function selectionActionCb(success, data) {
actions[o.selectedAction.name] && actions[o.selectedAction.name]({
success: success,
data: data
});
}
// opens up a net sheet modal against the selected property
function actionOpenNetSheet(property, enableControls) {
// calls SDK's openNetSheet method for the target property.
_self.openNetSheet(property.sa_property_id, property.customFullAddress);
// enable the controls back.
enableControls();
// invoke given action callback
selectionActionCb(true, {
message: 'Net Sheet Opened.'
});
}
// perform order report against the selected property
function actionOrderReport(property, enableControls) {
_self.orderReport({
sa_property_id: property.sa_property_id,
state_county_fips: property.mm_fips_state_code + property.mm_fips_muni_code,
report_type: 'property_profile',
output: 'link'
})
.then(function (res) {
var popup, reportLink;
reportLink = res.response.data.report.link;
// try auto-open a new tab to get the PDF file automatically.
popup = window.open(reportLink);
// for blocked-popups users, show and alert bar for the report link
if (!popup) {
// render the link to the success alert, and show it.
$('#ttb-sdk--instant-lookup--alert')
.show()
.find('a')
.attr('href', reportLink);
}
// enable the controls back.
enableControls();
// invoke given action callback
selectionActionCb(true, {
reportLink: reportLink
});
}, function () {
var errorMessage = 'Failed in getting full profile report.';
alert(errorMessage);
enableControls();
// invoke given action callback
selectionActionCb(false, {
message: errorMessage
});
});
}
},
/**
* This method renders a widget includes a connect button to open up the TTB integration modal which contains an <code>iframe</code> controlled by TTB. <br>
* <br>
* It uses <strong>localStorage</strong> of the host origin, to store the selected sponsor info as <code>ttb-sdk--connect--selected-sponsor</code>,
* It is a good gate for host sites to persist the user's sponsor selection over their servers, by reading/writing from/to it.
* Widget will pick it up whenever gets rendered.
* <br>
* (Make sure <strong>ttbSdk.min.css</strong> file is injected for proper style and look for the widgets.
* You can even check out working example over https://jsfiddle.net/benutech/qr7ykw9L/)
*
* @param {Object} options - configuration for the connect widget.
* @param {String} options.elementSelector - DOM element selector where the widget needs to be rendered.
* <code>#lorem</code> or <code>.ipsum</code> etc.
* @param {Object} options.loginRemotePayload - "stk" and "getuser_url" information to be used for login. please check .loginRemote() documentation for more.
*
* @param {Object} [actions] - The actions object contains mapping callbacks to be consumed on success or failure.
* @param {Function} [actions.onConnectSuccess] - To be invoked with <code>info</code> object,
* which on successful "Connect", contains <code>selectedSponsor</code> object, and <code>loginPerformed</code> flag (to be "true" when user gets auto logged in from widget modal)
* @param {Function} [actions.onConnectFailure] - To be invoked with <code>reason</code> {String} message on failing connecting.
*
* @example
*
* // with basic and minimum requirement.
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* var options = {
* elementSelector: '#ttb-connect-wrapper',
* loginRemotePayload: {
* stk: 'xxxxxxxxxxxxxxx'
* }
* };
*
* var $ttbConnect = ttb.connectWidget(options);
*
* @example
*
* // with advanced configuration for handling success, and failure of the connection process.
* var ttb = new TTB({ ... }); // skip if already instantiated.
*
* var options = {
* elementSelector: '#ttb-connect-wrapper'
* loginRemotePayload: {
* stk: 'xxxxxxxxxxxxxxx',
* getuser_url: 'https://www.yoursite.com/webservices/getuser.json'
* }
* };
*
* var actions = {
* onConnectSuccess: function(info) {
* // optional callback - to be called when done.
* // passed argument will be an "info" object which contains "selectedSponsor" which can be used to set instance sponsor.
* // note: required details from sponsorInfo already being written to localStorage as "ttb-sdk--connect--selected-sponsor" by SDK.
* },
*
* onConnectFailure: function(reason) {
* // optional callback - to be called when failed connecting.
* // passed argument will be:
* // reason {String} - Reason of the failure, e.g. "failed" if API did not connect.
* }
* };
*
* var $ttbConnect = ttb.connectWidget(options, actions);
*
* @return {Object | null} $element - JQuery reference to the rendered widget container element.
* OR null in case of any error in rendering widget. E.g. elementSelector did not found.
*
* */
connectWidget: function (options, actions) {
var o, ttb;
o = {};
o.$container = $(options.elementSelector);
// validate if target element not found
if (!o.$container.length) {
this._log(['connectWidget : abort : element not found - ', options.elementSelector]);
return null;
}
ttb = this;
actions = actions || {};
o.userProfile = undefined; // later to be filled via getUserProfile( )
o.widgetClass = 'ttb-sdk--connect--container';
o.widgetTemplate = [
'<div id="ttb-sdk--connect--select-section" class="row">',
' <div id="ttb-sdk--connect--alert" class="col-xs-9">',
defaults.errorMessages.CONNECT__NO_SPONSOR,
' </div>',
' <div id="ttb-sdk--connect--connect" class="col-xs-3">',
' <button type="button" class="btn btn-primary pull-right">Connect</button>',
' </div>',
'</div>',
'<!-- hidden by default -->',
'<div id="ttb-sdk--connect--change-section" class="row" style="display: none;">',
' <div class="col-xs-4">',
' <strong>Title company</strong>',
' </div>',
' <!-- to be dynamically updated -->',
' <div id="ttb-sdk--connect--company-name" class="col-xs-4">',
' - ',
' </div>',
' <div id="ttb-sdk--connect--change" class="col-xs-4">',
' <button type="button" class="btn btn-primary pull-right">Change</button>',
' </div>',
'</div>'
].join('');
// add required class for CSS
o.$container
.addClass(o.widgetClass)
// render the widget template
.append(o.widgetTemplate);
// for scope bootstrap instances.
if (window.TTB.scopedBootstrap) {
o.$container
.addClass(defaults.classScopedBootstrap)
.addClass(defaults.classScopedBootstrapBody);
}
// check for any existing connection - activate disconnected section UI.
checkForExistingSponsor();
// register handler of connect button to open up a connect modal
o.$container.find('#ttb-sdk--connect--select-section button').on('click', onSelectConnection);
o.$container.find('#ttb-sdk--connect--change-section button').on('click', onChangeConnection);
// opens up the connect modal
function onSelectConnection() {
var $connectModal, modalOptions, iframeOptions, origin;
ttb._log(['connectWidget: onSelectConnection: init.']);
modalOptions = {
id: 'ttb-sdk--connect--modal',
title: 'Connect with TitleToolbox'
};
// dev vs prod destination.
origin = window.location.port === defaults.devPortSandbox ?
('http://localhost:' + defaults.devPortLanding) : 'https://ttb-landing-page.herokuapp.com';
iframeOptions = {
id: 'ttb-sdk--connect--iframe',
height: '635px', // TODO should calculated against window height
origin: origin,
pathname: '/index.html',
params: {
stk: options.loginRemotePayload.stk,
getuser_url: options.loginRemotePayload.getuser_url,
//userProfile: JSON.stringify(o.userProfile),
partnerKey: ttb.config.partnerKey,
debug: ttb.debug
},
onMessage: onMessage
};
// render the sponsors TOS content via modal
$connectModal = window.TTB.utilIframeModal(modalOptions, iframeOptions);
// to be invoked when a "message" event is broadcast from the given iframe site
function onMessage(data, event) {
ttb._log(['connectWidget: onMessage', data]);
// skip for the unrelated message events
if (data.action.indexOf('TTB:SDK::CONNECT_WIDGET') === -1) {
return;
}
// close the connect widget modal.
$connectModal.modal('hide');
switch (data.action) {
// failure - invoke given callbacks
case 'TTB:SDK::CONNECT_WIDGET:ERROR':
onConnectFailure(data.info.reason, false);
break;
// success - store sponsor, and update UI
case 'TTB:SDK::CONNECT_WIDGET:SUCCESS':
onConnectSuccess(data.info, true);
break;
default:
ttb._log(['connectWidget: onMessage: unknown action', data.action]);
return;
}
}
}
// alias to "select connection"
function onChangeConnection() {
ttb._log(['connectWidget: onChangeConnection: called.']);
// call same "select connection" method for here too.
onSelectConnection();
}
// pulls user profile, and checks for last selected sponsor.
function checkForExistingSponsor() {
// leave wait msg for select-connection UI.
updateSelectConnectionMode('Pulling user profile...', true);
// get user profile first.
window.TTB.getUserProfile(options.loginRemotePayload, ttb.config.partnerKey)
.then(function (res) {
var payload;
// catch error - report and exit.
if (!res.data || !res.data.User) {
onConnectFailure('Failed in pulling user profile.', true);
return;
}
/* else - resume checking selected sponsor. */
// keep the user profile cached for later connect modal box use.
o.userProfile = res.data.User;
// leave wait msg for select-connection UI.
updateSelectConnectionMode('Checking current partner selection...', true);
payload = {
email: o.userProfile.email
};
window.TTB.getSponsorSelection(ttb.config.partnerKey, payload)
.then(function (res) {
var data, errorMessage;
res = res.response;
// catch error - report and exit.
if (res.status !== 'OK') {
// we keep "connect" enabled here.
errorMessage = res.data[0].indexOf('RC_ERR_105') >= 0 ?
defaults.errorMessages.CONNECT__NO_SPONSOR : res.data[0];
onConnectFailure(errorMessage, false);
return;
}
/* else - resume setting selected sponsor */
data = {
selectedSponsor: window.TTB.utilBuildSponsorInfo(res.data)
};
onConnectSuccess(data, false);
}, function (reason) {
// we keep "connect" enabled here.
onConnectFailure(defaults.errorMessages.GENERAL__CONNECT_FAILED + ' for pulling partner selection.', false);
});
}, function (reason) {
onConnectFailure(defaults.errorMessages.GENERAL__CONNECT_FAILED + ' for pulling user profile.', true);
});
}
// handles the connect failure
function onConnectFailure(reason, disableConnect) {
// leave error msg for select-connection UI.
updateSelectConnectionMode(reason, disableConnect);
// invoke the related action callback.
actions.onConnectFailure && actions.onConnectFailure(reason);
}
// saves the sponsor selection
function onConnectSuccess(info, loginPerformed) {
// update instance's selected sponsor
ttb.setSponsor(info.selectedSponsor);
// show change-connection UI
o.$container
.find('#ttb-sdk--connect--select-section').hide()
.next('#ttb-sdk--connect--change-section').show()
.find('#ttb-sdk--connect--company-name').text(ttb.sponsor.title || '');
// leave success msg for select-connection UI.
updateSelectConnectionMode('Partner selected.', true);
// invoke the related action callback.
actions.onConnectSuccess && actions.onConnectSuccess({
selectedSponsor: ttb.sponsor,
loginPerformed: loginPerformed
});
}
// renders state related messages / errors on select-connection UI.
function updateSelectConnectionMode(text, disableConnect) {
o.$container
.find('#ttb-sdk--connect--alert').text(text || '')
.next('#ttb-sdk--connect--connect')
.find('button').prop('disabled', disableConnect);
}
}
};
})();