Saturday 10 June 2017

Locker Service, Lightning Components and JavaScript Libraries

Locker Service, Lightning Components and JavaScript Libraries

Window

Introduction

As I’ve previously blogged, the Summer 17 release of Salesforce allows you to turn the locker service on or off based on the API version of a component. This is clearly an awesome feature, but there is a gotcha which I came across this week while working with the Lightning Testing Service Pilot.

I have a JavaScript library containing functionality that needs to work as a singleton (so a single instance that all Lightning Components have access to). This library is a static resource that is loaded via the <ltng:require /> standard component.

One window to rule them all?

In JavaScript world there is a single Window object, representing the browser’s window, which all global objects, functions and variables are attached to. When my JavaScript library is loaded, an immediately invoked function expression executes and attaches an my library object to the Window.

In Lightning Components world, with the locker service enabled, the single Window object changes somewhat. Instead of a Window, your components see a SecureWindow object which is shared among all components in the same namespace. This SecureWindow is isolated from the real Window for security reasons. In practice, this means that if you mix locker and non-locker Lightning Components on the same page, there are two different window concepts which know nothing about each other.

Example code 

The example here is a lightning application that attaches a String to the Window object when it initialises and includes two components that each attempt to access this variable from the window, one at ApI 40 and one at Api 39. The app also attempts to access the variable, just to show that it is correctly attached.

App

<aura:application >
    <aura:handler name="init" value="{!this}" action="{!c.doInit}" />
    <button onclick="{!c.showFromWindow}">Show from in App</button> <br/>
    <c:NonLockerWindow /> <br/>
    <c:LockerWindow /> <br/>
</aura:application>

Controller

({
	doInit : function(component, event, helper) {
		window.testValue="From the Window";
	},
	showFromWindow : function(component, event, helper) {
		alert('From window = ' + window.testValue);
	}
})

Locker Component

<aura:component >
	<button onclick="{!c.showFromWindow}">Show from Locker Component</button>
</aura:component>

Controller

({
	showFromWindow : function(component, event, helper) {
		alert('From window = ' + window.testValue);
	}
})

Non Locker Component

<aura:component >
	<button onclick="{!c.showFromWindow}">Show from non-Locker Window</button>
</aura:component>

Controller

({
	showFromWindow : function(component, event, helper) {
		alert('From window = ' + window.testValue);
	}
})

Executing the example

If I set the API version of the app to 40, this enables the locker service. Click the three buttons in turns shows the following alerts:

From App

Screen Shot 2017 06 10 at 06 31 24

As expected, the variable is defined.

Non-Locker Component

Screen Shot 2017 06 10 at 06 31 37

As the application is running with the locker service enabled, the variable is attached to a secure window. The non-locker service component cannot access this so the variable is undefined.

Locker Component


Screen Shot 2017 06 10 at 06 31 44

As this component is also executing the with the locker service enabled, it has access to the secure window for it’s namespace. As the namespace between the app and this component is the same, the variable is available.

Changing the app to API version 39 allows the non-locker component to access the variable from the regular JavaScript window, while the locker component doesn’t have access as the variable is not defined on the secure window.

So what?

This had a couple of effects on my code if I mix Api versions to that my contains a combination of locker and non-locker components:

  •  I can’t rely on the library being made available by the containing component or app. Thus I have to ensure that every component loads the static resource. This is best practice anyway, so not that big a deal
  • I don’t have a singleton library any more. While this might not sound like a big deal, given that I can load it into whatever window variant I have, it means that if I change something in that library from one of my components, it only affects the version attached to the window variant that my component currently have access. For example, if I set a flag to indicate debug mode is enabled, only those components with access to the specific window variant will pick this up. I suspect I’ll solve this by having two headless components that manage the singletons, one with API 40 and one with API < 40, and send an event to each of these to carry out the same action.

Related Posts

 

 

 

Sunday 4 June 2017

Visualforce Page Metrics in Summer 17

Visualforce Page Metrics in Summer 17

Metrics

Introduction

The Summer 17 release of Salesforce introduces the concept of Visualforce Page Metrics via the SOAP API. This new feature allows you to analyse how many page views your Visualforce pages received on a particular day. This strikes me as really useful functionality - I create a lot of Visualforce pages (although more Lightning Component based these days), to allow users to manage multiple objects on a single page for example. After I’ve gone to the effort of building the page I’m always curious as to whether anyone is actually using it!

SOAP Only

A slight downside to this feature is that the information is only available via the SOAP API. The release notes give an example of using the Salesforce Workbench, but ideally I’d like a Visualforce page to display this information without leaving my Salesforce org. Luckily, as I’ve observed in previous blog posts, the Ajax Toolkit provides a JavaScript wrapper around the SOAP API that can be accessed from Visualforce. 

Sample Page

In my example page I’m grouping the information by date and listing the pages that were accessed in order of popularity. There’s not much information in the page as yet because I’m executing this from a sandbox, so the page may get unwieldy in a production environment and need some pagination or filter criteria.

Screen Shot 2017 06 04 at 06 25 29

Show me the code

Once the Ajax Toolkit is setup, the following query is executed to retrieve all metrics:

var result = sforce.connection.query(
   "SELECT ApexPageId,DailyPageViewCount,Id,MetricsDate FROM VisualforceAccessMetrics " +
   "ORDER BY MetricsDate desc, DailyPageViewCount desc");

The results of the query can then be turned into an iterator and the records extracted - I’m storing these as an array in an object with a property per date:

var it = new sforce.QueryResultIterator(result);
        
while(it.hasNext()) {
    var record = it.next();
            
    var dEle=metricByDate[record.MetricsDate];
    if (!dEle) {
        dEle=[];
        metricByDate[record.MetricsDate]=dEle;
    }
            
    // add to the metrics organised by date
    dEle.push(record);
}
        

 This allows me to display the metrics by Visualforce page id, but that isn’t overly useful, so I query the Visualforce pages from the system and store them in an object with a property per id - analogous to an Apex map:

result = sforce.connection.query(
    "Select Id, Name from ApexPage order by Name desc");
        
it = new sforce.QueryResultIterator(result);
        
var pageNamesById={};
        
while(it.hasNext()) {
    var record = it.next();
    pageNamesById[record.Id]=record.Name;
}

 I can then iterate the date properties and output details of the Visualforce page metrics for those dates:

for (var dt in metricByDate) {
    if (metricByDate.hasOwnProperty(dt)) {
        var recs=metricByDate[dt];
        output+='<tr><th colspan="3" style="text-align:center; font-size:1.2em;">' + dt + '</td></tr>';
	output+='<tr><th>Page</th><th>Date</th><th>Views</th></tr>';
	for (var idx=0, len=recs.length; idx<len; idx++) {
            var rec=recs[idx];
            var name=pageNamesById[rec.ApexPageId];
            output+='<tr><td>' + name + '</td>';
            output+='<td>' + rec.MetricsDate + '</td>';
            output+='<td>' + rec.DailyPageViewCount + '</td></tr>';
        }
    }
}

You can see the full code at this gist.

Related Posts