report

Hosting Power BI Content - 6/6

We have reached the last episode of this quite long tale throughout F#, Power BI and WebSharper. In this post we'll see the embedding page, the "end product" of our application's pipeline. A more tr

November 10, 2017

We have reached the last episode of this quite long tale throughout F#, Power BI and WebSharper. In this post we'll see the embedding page, the "end product" of our application's pipeline.

A more traditional approach would be to direct the browser to the server when a user clicks on a report link. The server would then render the new page and respond to the browser that would only need to display it.

In this case, though, the html markup for the embedding pages is pretty identical and most of the metadata needed is already available to the browser. So it is relatively straightforward to follow a different pattern, where the "scarce" server resources are charged as less as possible and most of the work is done by the browser in the "abundant" client resources. In modern jargon, we are talking of SPA (Single Page Application), even if in this case there is actually a new page open, but it is created client side.

To achieve this goal

  • The embedding page HTML markup is served to the client only once, hidden in the main page. This markup contains only 4 placeholders to be populated when the report is selected; of the 4, 3 are also already embedded in the main page as data-... attributes (groupId, reportId, embedUrl), so the fourth one (embed token) is really the only missing piece to be asked to the server

  • When the user selects a report, the browser has already almost all (HTML markup, 3/4 of metadata), so it only has to ask for the embed token; no need to ask the server to re-build the full markup every time

The full HTML markup can be seen on github (Rep.html).

Let's repeat here the relevant parts from Main.cs, where Rep.html is embedded in a dedicated hidden division.

[code language="fsharp"] module FrontEnd = ... let private home workspaces rptPageHtml : Doc list = [ divAttr [attr.id "embedReportHtml"; attr.hidden "true"] [text rptPageHtml] powerBIAccordion workspaces ]

let bodyHome workspaces rptPageHtml: Doc list = [ //Doc.WebControl(new Web.Require()) divAttr [attr.``class`` "page-header"] [ h2Attr [attr.``class`` "text-center"] [text "Power BI Reports"] ] divAttr [attr.``class`` "jumbotron"] (home workspaces rptPageHtml) ]

module Templating = ... type Template = Templating.Template<"Template1.html">

let Home (ctx: Context) workspaces html = Content.Page( Template() .Body(FrontEnd.bodyHome workspaces html) .HomeActive("active") .Root(Util.appPath ctx) .Doc())

... module Site = let private HomePage (ctx: Context) = let path = sprintf "%sRep.html" ctx.RootFolder Templating.Home ctx (State.getWorkspaces()) (File.ReadAllText(path).Replace(@"${Root}", Util.appPath ctx)) [/code]

The most important part of Rep.html is the javascript code:

[code language="javascript"] (init = function () { var title = "${{rplcTitle}}";

var models = window['powerbi-client'].models; var permissions = models.Permissions.All;

var config = { type: 'report', tokenType: models.TokenType.Embed, accessToken: "${{rplcEmbedToken}}", embedUrl: "${{rplcEmbedUrl}}", id: "${{rplcReportId}}", permissions: permissions, settings: { filterPaneEnabled: true, navContentPaneEnabled: true } };

try { var reportContainer = document.getElementById('reportContainer'); var reportFrame = powerbi.embed(reportContainer, config); reportFrame.fullscreen(); document.title = title; } catch (err) { alert("Error: " + err); } })(); [/code]

The powerbi.embed javascript function does the magic, and is part of the Power BI javascript API, which together with the PowerBI .NET API we used in Main.fs, makes the Power BI Embedding SDK.

The only variable parts are those in the ${{...}} markup. There are replaced just before creating the embedding page, by client-side code copied here from Client.fs

[code language="fsharp"] let private openReport name embedUrl reportId embedToken = let html = JQuery("#embedReportHtml").Text() .Replace("${{rplcTitle}}", "NCF.BPP - " + name) .Replace("${{rplcEmbedToken}}", embedToken) .Replace("${{rplcEmbedUrl}}", embedUrl) .Replace("${{rplcReportId}}", reportId)

let view = JS.Window.Open() view.Document.Write(html) [/code]

After the replacement, the html is complete and can be injected in a new created page, operating only client-side with javascript and its DOM API.

Someone would wonder why I haven't opted to maintain the initial intention and write all the logic in F# using the WebSharper transpiler. Here the reasons:

  1. The Power BI community has already published javascript working examples using the Power BI javascript API

  2. WebSharper offers WIG to generate custom javascript interfaces, and the community did also publish a working example for power BI. However, in this first version of the project I didn't see enough motives to pursue this more difficult way, even if I am willing to go for it when the applicaiton will become more complex.

Conclusion

Here we go, I hope you found this series of some interest. Suggestion and constructive comments are welcome.

Related articles