When should I use JavaScript in widgets?
Widgets include JavaScript to perform client-side behavior including issuing REST requests against the REST platform API, issuing AJAX requests against custom widget-defined endpoints, rendering data via client-side templates, consuming the platform-provided JavaScript APIs, and generally providing richer user experiences.
Registering JavaScript
When registering JavaScript, please note:
- Always register script blocks using the #registerEndOfPageHtml directive (see below).
- If more than a few lines, places the Javascript in an external script file and link to it using $core_widget.GetFileUrl('FILENAME').
- JavaScript modules should be defined within a vendor-specific namespace extending jQuery. Core widgets' scripts are placed within the jQuery.telligent.evolution.widgets namespace within an object named according to the instance identifier of the widget. For example, the Activity Story Stream widget's JavaScript is located in $.telligent.evolution.widgets.activityStoryStream.
- Only expose necessary methods publicly on your module.
To facilitate registering <script></script> blocks at the bottom of the page, the #registerEndOfPageHtml directive is provided. Its syntax is:
#registerEndOfPageHtml(uniqueRegistrationKey) // Markup To render at the end of the page. #end
The markup block within the directive will be rendered at the end of the rendered HTML page.
A few notes about this directive:
- In preview mode, markup will be rendered inline instead of at the bottom of the page. This ensures that the JavaScript will be properly rendered to the client when editing pages.
- The uniqueRegistrationKey is optional. For scripts that should always be registered (for example, scripts that configure the current instance of a widget), the directive can be called without the parameter. If the parameter is specified, the block will only be rendered once for the given key, even if multiple widgets attempt to register HTML using the same key.
- A common pattern is to use a uniqueRegistrationKey parameter when registering an included, external, script, but without a key when instantiating that script's module.
Registering inline JavaScript
A simple inclusion of inline JavaScript within a widget's Velocity content would look like:
#registerEndOfPageHtml() <script> alert("Hello from JavaScript!"); </script> #end
Registering JavaScript widget attachments
As an example of including a widget-defined JavaScript file, this is a script of an assumed widget named myWidget stored within a widget attachment as ui.js.
(function($){ // namespace client code to avoid collisions $.myNameSpace.myWidget = { register: function(options) { // initialize client script using passed options }; } })(jQuery);
The script attachment can then be referenced and called with the following:
#registerEndOfPageHtml('myNameSpace.myWidget') <script type="text/javascript" src="$core_v2_encoding.HtmlAttributeEncode($core_v2_widget.GetFileUrl('ui.js'))"></script> #end #registerEndOfPageHtml() <script type="text/javascript"> jQuery(document).ready(function(){ jQuery.myNameSpace.myWidget.register({ key1: 'value1', key2: 'value2' }); }); </script> #end
Please note:
- The use of $core_v2_widget.GetFileUrl() to obtain a URL for loading the widget-defined attachment, ui.js.
- Passing of widget instance-specific data to the script-defined myWidget module.
- when registering via the #registerEndOfPageHtml() directive, the registration occurs when executed. If the widget is later not rendered (either by not rendering any content or by calling $core_widget.Hide()), the HTML reigstered to be rendered at the end of the page will still render. It is important to call $core_widget.Hide() before any end-of-page HTML registration is performed.
Accessing the REST API
Verint Community includes the following custom extensions to jQuery's AJAX API to facilitate interactions with REST and callbacks to Verint Community.
jQuery.telligent.evolution.get(options) jQuery.telligent.evolution.post(options) jQuery.telligent.evolution.put(options) jQuery.telligent.evolution.del(options)
These functions have the same API as jQuery.ajax() but with the following benefits:
- Verint Community auth tokens are automatically pre-set on the requests' headers. Security "just works"
- The proper Rest-Method verb overload header is added when it's a PUT or DELETE
- Items in the data option are automatically interpolated into the parameterized Endpoint URL as demonstrated below.
- REST api endpoints' parameters are automatically uri-encoded
- Multiple REST requests can be batched into parallel or sequential unified requests
As an example of making a simple REST GET request, the following requests the name of the community and displays it in an alert.
jQuery.telligent.evolution.get({ url: jQuery.telligent.evolution.site.getBaseUrl() + 'api.ashx/v2/info.json', success: function(response) { alert('Hello from ' + response.InfoResult.SiteName); } });
Note the use of getBaseUrl() prepended to the REST URL. This endpoint and all others are defined in the REST API documentation.
When using these REST functions, the values passed through the data parameter that are also identified within the provided url are replaced. Any data values not found in the url are passed either as query string data (for the get method) or POST data (for post, put, and del methods). For example, the {GroupId} and {UserId} parameters in the url will be replaced with the values defined in the data parameter:
jQuery.telligent.evolution.del({ url: jQuery.telligent.evolution.site.getBaseUrl() + 'api.ashx/v2/groups/{GroupId}/members/users/{UserId}.json', data: { GroupId: 5, UserId: 2107 }, success: function(response) { console.log(response); } });
JavaScript REST API examples
Deleting a user from a group
Note that the {GroupId} and {UserId} parameters in the URL are left parameterized, and items in data will resolve to the URL before it is requested.
jQuery.telligent.evolution.del({ url: $.telligent.evolution.site.getBaseUrl() + '/api.ashx/v2/groups/{GroupId}/members/users/{UserId}.json', data: { GroupId: 5, UserId: 2107 }, success: function(response) { console.log(response); } });
Add a message to an existing conversation
Note that in this case, Id is interpolated into the raw API endpoint as /api.ashx/v2/conversations/50.json, but Subject and Body are still passed as post parameters, and Id is not
jQuery.telligent.evolution.post({ url: $.telligent.evolution.site.getBaseUrl() + '/api.ashx/v2/conversations/{Id}.json', data: { Id: 50, Subject: "New Mesage Subject", Body: "New Message Body" }, success: function(response) { console.log(response); } });
Request Conversations
This is a more traditional scenario, where PageSize and PageIndex are passed as query string parameters to the URL, which needs no interpolation.
jQuery.telligent.evolution.get({ url: $.telligent.evolution.site.getBaseUrl() + '/api.ashx/v2/conversations.json', data: { PageSize: 10, PageIndex: 3 }, success: function(response) { console.log(response); } });
REST Batching
Multiple REST requests can be batched into single HTTP requests, and occur either in parallel or in sequence. This can sometimes have the advantage of providing a more efficient user experience.
The simplest way to use REST batching is with the jQuery.telligent.evolution.batch function. batch() is a high-level wrapper which runs all wrapped REST requests in a single batched HTTP request. Each REST request still supports individual success/error callbacks as well as individual promise callbacks, though these callbacks are not executed until the batch completes. It is not necessary to pass an explicit batch id to REST requests performed within a call to batch(). Batches created using this method cannot be nested. Batching returns a promise which resolves when the entire batch completes.
A batched REST request looks like:
$.telligent.evolution.batch(function() { // ordinary REST requests // $.telligent.evolution.post({...}) // $.telligent.evolution.get({...}) // $.telligent.evolution.put({...}) }, options);
By default, batches run in parallel. To run batches sequentially, pass the option of sequential: true. A sequential batch that contains a failed request will halt all subsequent requests in the batch and reject the entire batch promise. Any successful sub-requests are still resolved. Parallel requests that contain one or more failed requests still resolve the entire batch and do not halt other sub requests.
In addition to the high level batch() method, lower level functions also exist for defining and using batches, including:
var batchId = $.telligent.evolution.batch.start();
start() is a lower-level method for specifically creating a batch context. Calls to get(), post(), put(), and del() support a batch parameter which defers their execution until the associated batch is run. Batch contexts expire after 60 seconds of not being executed.
$.telligent.evolution.batch.process(batchId, options);
process() is a lower-level method which runs a batched REST request with all REST requests associated with that batch ID. Returns a promise which resolves when the entire batch completes. Options supported are the same as when passing to the batch() wrapper.
It is usually simpler to use the higher-level $.telligent.evolution.batch() method instead, except in cases where it isn't feasible to wrap all REST requests in a single function scope.
Custom Widget-Defined AJAX Endpoints
Whenever possible, the REST API should be preferred when AJAX functionality is needed from within widgets. However, there are occasions where custom AJAX endpoints are desirable, including:
- Complex and expensive custom formatting that could be easier achieved on a server-side AJAX handler rather than piecing together the results of multiple (even batched) REST API calls and templating via the client-side
- When the REST API does not expose some portion of the Platform API which a script extension does support
Widgets support custom AJAX endpoints implemented as Velocity Script which can be referenced from the widget using a special URL format which executes the linked file when requested.
Widget-defined endpoints are one of a handful of ways of defining custom handlers in Community, including custom REST endpoints and custom HTTP handlers. Widget endpoints are best when the data is only needed by a single widget. HTTP handler plugins are best when needed by a plugin's client-side behavior, but not intended to be publicly accessible. REST plugins are best when intended to be publicly available alongside platform-defined REST endpoints.
Custom Widget Endpoint API:
Custom endpoints require use of $core_v2_widget.GetExecutedFileUrl()
$core_v2_widget.GetExecutedFileUrl('script.vm')
GetExecutedFileUrl returns a URL which, when requested against, will execute the velocity script named via its parameter. The result of the execution will be returned as the response of the HTTP request. The script will have the same context available to it as the calling widget.
Custom Widget Endpoint Examples:
Calling a custom AJAX endpoint via GET and returning an HTML fragment
The following example returns a given user's latest status update formatted as an HTML fragment.
Assuming a widget attachment named lateststatus.vm with the following velocity script:
#set ($userId = $core_v2_page.GetQueryStringValue('w_userId')) #set($messages = $core_v2_statusMessage.List("%{ UserId = $userId, PageSize = 1 }")) #foreach($message in $messages) <span class="status-message">$message.Body</span> <span class="status-date">$core_v2_language.FormatAgoDate($message.CreatedDate)</span> #end
A client script which can call the custom endpoint. Note the use of GetExecutedFileUrl as well as the parameter prefix of "w_" to avoid collision with other query string parameters passed to the endpoint.
jQuery.telligent.evolution.get({ url: '$core_v2_encoding.JavascriptEncode($core_v2_widget.GetExecutedFileUrl('lateststatus.vm'))', data: { w_userId: 1234 }, success: function(response){ // add the response to the page jQuery('#element').html(response); } });
Calling a custom AJAX endpoint via POST and returning JSON
Custom AJAX endpoints can also return JSON, which can be a useful pattern when exposing a Velocity API that isn't also available via REST, such as a custom extension or the custom response of a collection of Velocity method calls that produces a single, processed, unified data response.
As an example of exposing a Velocity Widget Extension to JavaScript via a custom widget endpoint that returns JSON, let's consider $core_v2_language.Truncate(). This method performs advanced server-side word-aware truncation. Let's consider a widget wants to consume this via JavaScript.
Assuming a widget attachment named truncate.vm,
#if ($core_v2_page.IsPost) #set($text = $core_v2_page.GetFormValue('w_text')) #set($length = $core_v2_utility.ParseInt($core_v2_page.GetFormValue('w_length'))) #set($truncatedText = $core_v2_language.Truncate($text, $length)) $core_v2_page.SetContentType('application/json') { "truncated": "$core_v2_encoding.JavascriptEncode($truncatedText)" } #end
Client script which can call the custom endpoint. Note the use of GetExecutedFileUrl
jQuery.telligent.evolution.post({ url: '$core_v2_encoding.JavascriptEncode($core_v2_widget.GetExecutedFileUrl('truncate.vm'))', data: { w_text: 'Test Text To Truncate' id: 6, }, dataType: 'json', success: function(response){ alert('Truncated response: ' + response.truncated); } });
For more information
- Platform REST API - All platform REST APIs
- JavaScript APIs - All platform-provided JavaScript UI APIs